First Commit
This commit is contained in:
@@ -0,0 +1,726 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace ColourHelpers
|
||||
{
|
||||
static uint8 floatToUInt8 (float n) noexcept
|
||||
{
|
||||
return n <= 0.0f ? 0 : (n >= 1.0f ? 255 : (uint8) roundToInt (n * 255.0f));
|
||||
}
|
||||
|
||||
static float getHue (Colour col)
|
||||
{
|
||||
auto r = (int) col.getRed();
|
||||
auto g = (int) col.getGreen();
|
||||
auto b = (int) col.getBlue();
|
||||
|
||||
auto hi = jmax (r, g, b);
|
||||
auto lo = jmin (r, g, b);
|
||||
|
||||
float hue = 0.0f;
|
||||
|
||||
if (hi > 0 && ! exactlyEqual (hi, lo))
|
||||
{
|
||||
auto invDiff = 1.0f / (float) (hi - lo);
|
||||
|
||||
auto red = (float) (hi - r) * invDiff;
|
||||
auto green = (float) (hi - g) * invDiff;
|
||||
auto blue = (float) (hi - b) * invDiff;
|
||||
|
||||
if (r == hi) hue = blue - green;
|
||||
else if (g == hi) hue = 2.0f + red - blue;
|
||||
else hue = 4.0f + green - red;
|
||||
|
||||
hue *= 1.0f / 6.0f;
|
||||
|
||||
if (hue < 0.0f)
|
||||
hue += 1.0f;
|
||||
}
|
||||
|
||||
return hue;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct HSL
|
||||
{
|
||||
HSL (Colour col) noexcept
|
||||
{
|
||||
auto r = (int) col.getRed();
|
||||
auto g = (int) col.getGreen();
|
||||
auto b = (int) col.getBlue();
|
||||
|
||||
auto hi = jmax (r, g, b);
|
||||
auto lo = jmin (r, g, b);
|
||||
|
||||
if (hi < 0)
|
||||
return;
|
||||
|
||||
lightness = ((float) (hi + lo) / 2.0f) / 255.0f;
|
||||
|
||||
if (lightness <= 0.0f)
|
||||
return;
|
||||
|
||||
hue = getHue (col);
|
||||
|
||||
if (1.0f <= lightness)
|
||||
return;
|
||||
|
||||
auto denominator = 1.0f - std::abs ((2.0f * lightness) - 1.0f);
|
||||
saturation = ((float) (hi - lo) / 255.0f) / denominator;
|
||||
}
|
||||
|
||||
Colour toColour (Colour original) const noexcept
|
||||
{
|
||||
return Colour::fromHSL (hue, saturation, lightness, original.getAlpha());
|
||||
}
|
||||
|
||||
static PixelARGB toRGB (float h, float s, float l, uint8 alpha) noexcept
|
||||
{
|
||||
auto v = l < 0.5f ? l * (1.0f + s) : l + s - (l * s);
|
||||
|
||||
if (approximatelyEqual (v, 0.0f))
|
||||
return PixelARGB (alpha, 0, 0, 0);
|
||||
|
||||
auto min = (2.0f * l) - v;
|
||||
auto sv = (v - min) / v;
|
||||
|
||||
h = ((h - std::floor (h)) * 360.0f) / 60.0f;
|
||||
auto f = h - std::floor (h);
|
||||
auto vsf = v * sv * f;
|
||||
auto mid1 = min + vsf;
|
||||
auto mid2 = v - vsf;
|
||||
|
||||
if (h < 1.0f) return PixelARGB (alpha, floatToUInt8 (v), floatToUInt8 (mid1), floatToUInt8 (min));
|
||||
else if (h < 2.0f) return PixelARGB (alpha, floatToUInt8 (mid2), floatToUInt8 (v), floatToUInt8 (min));
|
||||
else if (h < 3.0f) return PixelARGB (alpha, floatToUInt8 (min), floatToUInt8 (v), floatToUInt8 (mid1));
|
||||
else if (h < 4.0f) return PixelARGB (alpha, floatToUInt8 (min), floatToUInt8 (mid2), floatToUInt8 (v));
|
||||
else if (h < 5.0f) return PixelARGB (alpha, floatToUInt8 (mid1), floatToUInt8 (min), floatToUInt8 (v));
|
||||
else if (h < 6.0f) return PixelARGB (alpha, floatToUInt8 (v), floatToUInt8 (min), floatToUInt8 (mid2));
|
||||
|
||||
return PixelARGB (alpha, 0, 0, 0);
|
||||
}
|
||||
|
||||
float hue = 0.0f, saturation = 0.0f, lightness = 0.0f;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct HSB
|
||||
{
|
||||
HSB (Colour col) noexcept
|
||||
{
|
||||
auto r = (int) col.getRed();
|
||||
auto g = (int) col.getGreen();
|
||||
auto b = (int) col.getBlue();
|
||||
|
||||
auto hi = jmax (r, g, b);
|
||||
auto lo = jmin (r, g, b);
|
||||
|
||||
if (hi > 0)
|
||||
{
|
||||
saturation = (float) (hi - lo) / (float) hi;
|
||||
|
||||
if (saturation > 0.0f)
|
||||
hue = getHue (col);
|
||||
|
||||
brightness = (float) hi / 255.0f;
|
||||
}
|
||||
}
|
||||
|
||||
Colour toColour (Colour original) const noexcept
|
||||
{
|
||||
return Colour (hue, saturation, brightness, original.getAlpha());
|
||||
}
|
||||
|
||||
static PixelARGB toRGB (float h, float s, float v, uint8 alpha) noexcept
|
||||
{
|
||||
v = jlimit (0.0f, 255.0f, v * 255.0f);
|
||||
auto intV = (uint8) roundToInt (v);
|
||||
|
||||
if (s <= 0)
|
||||
return PixelARGB (alpha, intV, intV, intV);
|
||||
|
||||
s = jmin (1.0f, s);
|
||||
h = ((h - std::floor (h)) * 360.0f) / 60.0f;
|
||||
auto f = h - std::floor (h);
|
||||
auto x = (uint8) roundToInt (v * (1.0f - s));
|
||||
|
||||
if (h < 1.0f) return PixelARGB (alpha, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x);
|
||||
if (h < 2.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - s * f)), intV, x);
|
||||
if (h < 3.0f) return PixelARGB (alpha, x, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))));
|
||||
if (h < 4.0f) return PixelARGB (alpha, x, (uint8) roundToInt (v * (1.0f - s * f)), intV);
|
||||
if (h < 5.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x, intV);
|
||||
return PixelARGB (alpha, intV, x, (uint8) roundToInt (v * (1.0f - s * f)));
|
||||
}
|
||||
|
||||
float hue = 0.0f, saturation = 0.0f, brightness = 0.0f;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct YIQ
|
||||
{
|
||||
YIQ (Colour c) noexcept
|
||||
{
|
||||
auto r = c.getFloatRed();
|
||||
auto g = c.getFloatGreen();
|
||||
auto b = c.getFloatBlue();
|
||||
|
||||
y = 0.2999f * r + 0.5870f * g + 0.1140f * b;
|
||||
i = 0.5957f * r - 0.2744f * g - 0.3212f * b;
|
||||
q = 0.2114f * r - 0.5225f * g - 0.3113f * b;
|
||||
alpha = c.getFloatAlpha();
|
||||
}
|
||||
|
||||
Colour toColour() const noexcept
|
||||
{
|
||||
return Colour::fromFloatRGBA (y + 0.9563f * i + 0.6210f * q,
|
||||
y - 0.2721f * i - 0.6474f * q,
|
||||
y - 1.1070f * i + 1.7046f * q,
|
||||
alpha);
|
||||
}
|
||||
|
||||
float y = 0.0f, i = 0.0f, q = 0.0f, alpha = 0.0f;
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool Colour::operator== (const Colour& other) const noexcept { return argb.getNativeARGB() == other.argb.getNativeARGB(); }
|
||||
bool Colour::operator!= (const Colour& other) const noexcept { return argb.getNativeARGB() != other.argb.getNativeARGB(); }
|
||||
|
||||
//==============================================================================
|
||||
Colour::Colour (uint32 col) noexcept
|
||||
: argb (static_cast<uint8> ((col >> 24) & 0xff),
|
||||
static_cast<uint8> ((col >> 16) & 0xff),
|
||||
static_cast<uint8> ((col >> 8) & 0xff),
|
||||
static_cast<uint8> (col & 0xff))
|
||||
{
|
||||
}
|
||||
|
||||
Colour::Colour (uint8 red, uint8 green, uint8 blue) noexcept
|
||||
{
|
||||
argb.setARGB (0xff, red, green, blue);
|
||||
}
|
||||
|
||||
Colour Colour::fromRGB (uint8 red, uint8 green, uint8 blue) noexcept
|
||||
{
|
||||
return Colour (red, green, blue);
|
||||
}
|
||||
|
||||
Colour::Colour (uint8 red, uint8 green, uint8 blue, uint8 alpha) noexcept
|
||||
{
|
||||
argb.setARGB (alpha, red, green, blue);
|
||||
}
|
||||
|
||||
Colour Colour::fromRGBA (uint8 red, uint8 green, uint8 blue, uint8 alpha) noexcept
|
||||
{
|
||||
return Colour (red, green, blue, alpha);
|
||||
}
|
||||
|
||||
Colour::Colour (uint8 red, uint8 green, uint8 blue, float alpha) noexcept
|
||||
{
|
||||
argb.setARGB (ColourHelpers::floatToUInt8 (alpha), red, green, blue);
|
||||
}
|
||||
|
||||
Colour Colour::fromFloatRGBA (float red, float green, float blue, float alpha) noexcept
|
||||
{
|
||||
return Colour (ColourHelpers::floatToUInt8 (red),
|
||||
ColourHelpers::floatToUInt8 (green),
|
||||
ColourHelpers::floatToUInt8 (blue), alpha);
|
||||
}
|
||||
|
||||
Colour::Colour (float hue, float saturation, float brightness, float alpha) noexcept
|
||||
: argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, ColourHelpers::floatToUInt8 (alpha)))
|
||||
{
|
||||
}
|
||||
|
||||
Colour Colour::fromHSV (float hue, float saturation, float brightness, float alpha) noexcept
|
||||
{
|
||||
return Colour (hue, saturation, brightness, alpha);
|
||||
}
|
||||
|
||||
Colour Colour::fromHSL (float hue, float saturation, float lightness, float alpha) noexcept
|
||||
{
|
||||
Colour hslColour;
|
||||
hslColour.argb = ColourHelpers::HSL::toRGB (hue, saturation, lightness, ColourHelpers::floatToUInt8 (alpha));
|
||||
|
||||
return hslColour;
|
||||
}
|
||||
|
||||
Colour::Colour (float hue, float saturation, float brightness, uint8 alpha) noexcept
|
||||
: argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, alpha))
|
||||
{
|
||||
}
|
||||
|
||||
Colour::Colour (PixelARGB argb_) noexcept
|
||||
: argb (argb_)
|
||||
{
|
||||
}
|
||||
|
||||
Colour::Colour (PixelRGB rgb) noexcept
|
||||
: argb (Colour (rgb.getInARGBMaskOrder()).argb)
|
||||
{
|
||||
}
|
||||
|
||||
Colour::Colour (PixelAlpha alpha) noexcept
|
||||
: argb (Colour (alpha.getInARGBMaskOrder()).argb)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
PixelARGB Colour::getPixelARGB() const noexcept
|
||||
{
|
||||
PixelARGB p (argb);
|
||||
p.premultiply();
|
||||
return p;
|
||||
}
|
||||
|
||||
PixelARGB Colour::getNonPremultipliedPixelARGB() const noexcept
|
||||
{
|
||||
return argb;
|
||||
}
|
||||
|
||||
uint32 Colour::getARGB() const noexcept
|
||||
{
|
||||
return argb.getInARGBMaskOrder();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool Colour::isTransparent() const noexcept
|
||||
{
|
||||
return getAlpha() == 0;
|
||||
}
|
||||
|
||||
bool Colour::isOpaque() const noexcept
|
||||
{
|
||||
return getAlpha() == 0xff;
|
||||
}
|
||||
|
||||
Colour Colour::withAlpha (uint8 newAlpha) const noexcept
|
||||
{
|
||||
PixelARGB newCol (argb);
|
||||
newCol.setAlpha (newAlpha);
|
||||
return Colour (newCol);
|
||||
}
|
||||
|
||||
Colour Colour::withAlpha (float newAlpha) const noexcept
|
||||
{
|
||||
jassert (newAlpha >= 0 && newAlpha <= 1.0f);
|
||||
|
||||
PixelARGB newCol (argb);
|
||||
newCol.setAlpha (ColourHelpers::floatToUInt8 (newAlpha));
|
||||
return Colour (newCol);
|
||||
}
|
||||
|
||||
Colour Colour::withMultipliedAlpha (float alphaMultiplier) const noexcept
|
||||
{
|
||||
jassert (alphaMultiplier >= 0);
|
||||
|
||||
PixelARGB newCol (argb);
|
||||
newCol.setAlpha ((uint8) jmin (0xff, roundToInt (alphaMultiplier * newCol.getAlpha())));
|
||||
return Colour (newCol);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Colour Colour::overlaidWith (Colour src) const noexcept
|
||||
{
|
||||
auto destAlpha = getAlpha();
|
||||
|
||||
if (destAlpha <= 0)
|
||||
return src;
|
||||
|
||||
auto invA = 0xff - (int) src.getAlpha();
|
||||
auto resA = 0xff - (((0xff - destAlpha) * invA) >> 8);
|
||||
|
||||
if (resA <= 0)
|
||||
return *this;
|
||||
|
||||
auto da = (invA * destAlpha) / resA;
|
||||
|
||||
return Colour ((uint8) (src.getRed() + ((((int) getRed() - src.getRed()) * da) >> 8)),
|
||||
(uint8) (src.getGreen() + ((((int) getGreen() - src.getGreen()) * da) >> 8)),
|
||||
(uint8) (src.getBlue() + ((((int) getBlue() - src.getBlue()) * da) >> 8)),
|
||||
(uint8) resA);
|
||||
}
|
||||
|
||||
Colour Colour::interpolatedWith (Colour other, float proportionOfOther) const noexcept
|
||||
{
|
||||
if (proportionOfOther <= 0)
|
||||
return *this;
|
||||
|
||||
if (proportionOfOther >= 1.0f)
|
||||
return other;
|
||||
|
||||
PixelARGB c1 (getPixelARGB());
|
||||
PixelARGB c2 (other.getPixelARGB());
|
||||
c1.tween (c2, (uint32) roundToInt (proportionOfOther * 255.0f));
|
||||
c1.unpremultiply();
|
||||
|
||||
return Colour (c1);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
float Colour::getFloatRed() const noexcept { return getRed() / 255.0f; }
|
||||
float Colour::getFloatGreen() const noexcept { return getGreen() / 255.0f; }
|
||||
float Colour::getFloatBlue() const noexcept { return getBlue() / 255.0f; }
|
||||
float Colour::getFloatAlpha() const noexcept { return getAlpha() / 255.0f; }
|
||||
|
||||
//==============================================================================
|
||||
void Colour::getHSB (float& h, float& s, float& v) const noexcept
|
||||
{
|
||||
ColourHelpers::HSB hsb (*this);
|
||||
h = hsb.hue;
|
||||
s = hsb.saturation;
|
||||
v = hsb.brightness;
|
||||
}
|
||||
|
||||
void Colour::getHSL (float& h, float& s, float& l) const noexcept
|
||||
{
|
||||
ColourHelpers::HSL hsl (*this);
|
||||
h = hsl.hue;
|
||||
s = hsl.saturation;
|
||||
l = hsl.lightness;
|
||||
}
|
||||
|
||||
float Colour::getHue() const noexcept { return ColourHelpers::HSB (*this).hue; }
|
||||
float Colour::getSaturation() const noexcept { return ColourHelpers::HSB (*this).saturation; }
|
||||
float Colour::getBrightness() const noexcept { return ColourHelpers::HSB (*this).brightness; }
|
||||
|
||||
float Colour::getSaturationHSL() const noexcept { return ColourHelpers::HSL (*this).saturation; }
|
||||
float Colour::getLightness() const noexcept { return ColourHelpers::HSL (*this).lightness; }
|
||||
|
||||
Colour Colour::withHue (float h) const noexcept { ColourHelpers::HSB hsb (*this); hsb.hue = h; return hsb.toColour (*this); }
|
||||
Colour Colour::withSaturation (float s) const noexcept { ColourHelpers::HSB hsb (*this); hsb.saturation = s; return hsb.toColour (*this); }
|
||||
Colour Colour::withBrightness (float v) const noexcept { ColourHelpers::HSB hsb (*this); hsb.brightness = v; return hsb.toColour (*this); }
|
||||
|
||||
Colour Colour::withSaturationHSL (float s) const noexcept { ColourHelpers::HSL hsl (*this); hsl.saturation = s; return hsl.toColour (*this); }
|
||||
Colour Colour::withLightness (float l) const noexcept { ColourHelpers::HSL hsl (*this); hsl.lightness = l; return hsl.toColour (*this); }
|
||||
|
||||
float Colour::getPerceivedBrightness() const noexcept
|
||||
{
|
||||
return std::sqrt (0.241f * square (getFloatRed())
|
||||
+ 0.691f * square (getFloatGreen())
|
||||
+ 0.068f * square (getFloatBlue()));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Colour Colour::withRotatedHue (float amountToRotate) const noexcept
|
||||
{
|
||||
ColourHelpers::HSB hsb (*this);
|
||||
hsb.hue += amountToRotate;
|
||||
return hsb.toColour (*this);
|
||||
}
|
||||
|
||||
Colour Colour::withMultipliedSaturation (float amount) const noexcept
|
||||
{
|
||||
ColourHelpers::HSB hsb (*this);
|
||||
hsb.saturation = jmin (1.0f, hsb.saturation * amount);
|
||||
return hsb.toColour (*this);
|
||||
}
|
||||
|
||||
Colour Colour::withMultipliedSaturationHSL (float amount) const noexcept
|
||||
{
|
||||
ColourHelpers::HSL hsl (*this);
|
||||
hsl.saturation = jmin (1.0f, hsl.saturation * amount);
|
||||
return hsl.toColour (*this);
|
||||
}
|
||||
|
||||
Colour Colour::withMultipliedBrightness (float amount) const noexcept
|
||||
{
|
||||
ColourHelpers::HSB hsb (*this);
|
||||
hsb.brightness = jmin (1.0f, hsb.brightness * amount);
|
||||
return hsb.toColour (*this);
|
||||
}
|
||||
|
||||
Colour Colour::withMultipliedLightness (float amount) const noexcept
|
||||
{
|
||||
ColourHelpers::HSL hsl (*this);
|
||||
hsl.lightness = jmin (1.0f, hsl.lightness * amount);
|
||||
return hsl.toColour (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Colour Colour::brighter (float amount) const noexcept
|
||||
{
|
||||
jassert (amount >= 0.0f);
|
||||
amount = 1.0f / (1.0f + amount);
|
||||
|
||||
return Colour ((uint8) (255 - (amount * (255 - getRed()))),
|
||||
(uint8) (255 - (amount * (255 - getGreen()))),
|
||||
(uint8) (255 - (amount * (255 - getBlue()))),
|
||||
getAlpha());
|
||||
}
|
||||
|
||||
Colour Colour::darker (float amount) const noexcept
|
||||
{
|
||||
jassert (amount >= 0.0f);
|
||||
amount = 1.0f / (1.0f + amount);
|
||||
|
||||
return Colour ((uint8) (amount * getRed()),
|
||||
(uint8) (amount * getGreen()),
|
||||
(uint8) (amount * getBlue()),
|
||||
getAlpha());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Colour Colour::greyLevel (float brightness) noexcept
|
||||
{
|
||||
auto level = ColourHelpers::floatToUInt8 (brightness);
|
||||
return Colour (level, level, level);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Colour Colour::contrasting (float amount) const noexcept
|
||||
{
|
||||
return overlaidWith ((getPerceivedBrightness() >= 0.5f
|
||||
? Colours::black
|
||||
: Colours::white).withAlpha (amount));
|
||||
}
|
||||
|
||||
Colour Colour::contrasting (Colour target, float minContrast) const noexcept
|
||||
{
|
||||
ColourHelpers::YIQ bg (*this);
|
||||
ColourHelpers::YIQ fg (target);
|
||||
|
||||
if (std::abs (bg.y - fg.y) >= minContrast)
|
||||
return target;
|
||||
|
||||
auto y1 = jmax (0.0f, bg.y - minContrast);
|
||||
auto y2 = jmin (1.0f, bg.y + minContrast);
|
||||
fg.y = (std::abs (y1 - bg.y) > std::abs (y2 - bg.y)) ? y1 : y2;
|
||||
|
||||
return fg.toColour();
|
||||
}
|
||||
|
||||
Colour Colour::contrasting (Colour colour1,
|
||||
Colour colour2) noexcept
|
||||
{
|
||||
auto b1 = colour1.getPerceivedBrightness();
|
||||
auto b2 = colour2.getPerceivedBrightness();
|
||||
float best = 0.0f, bestDist = 0.0f;
|
||||
|
||||
for (float i = 0.0f; i < 1.0f; i += 0.02f)
|
||||
{
|
||||
auto d1 = std::abs (i - b1);
|
||||
auto d2 = std::abs (i - b2);
|
||||
auto dist = jmin (d1, d2, 1.0f - d1, 1.0f - d2);
|
||||
|
||||
if (dist > bestDist)
|
||||
{
|
||||
best = i;
|
||||
bestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
return colour1.overlaidWith (colour2.withMultipliedAlpha (0.5f))
|
||||
.withBrightness (best);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String Colour::toString() const
|
||||
{
|
||||
return String::toHexString ((int) argb.getInARGBMaskOrder());
|
||||
}
|
||||
|
||||
Colour Colour::fromString (StringRef encodedColourString)
|
||||
{
|
||||
return Colour (CharacterFunctions::HexParser<uint32>::parse (encodedColourString.text));
|
||||
}
|
||||
|
||||
String Colour::toDisplayString (const bool includeAlphaValue) const
|
||||
{
|
||||
return String::toHexString ((int) (argb.getInARGBMaskOrder() & (includeAlphaValue ? 0xffffffff : 0xffffff)))
|
||||
.paddedLeft ('0', includeAlphaValue ? 8 : 6)
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
class ColourTests final : public UnitTest
|
||||
{
|
||||
public:
|
||||
ColourTests()
|
||||
: UnitTest ("Colour", UnitTestCategories::graphics)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
auto testColour = [this] (Colour colour,
|
||||
uint8 expectedRed, uint8 expectedGreen, uint8 expectedBlue,
|
||||
uint8 expectedAlpha = 255, float expectedFloatAlpha = 1.0f)
|
||||
{
|
||||
expectEquals (colour.getRed(), expectedRed);
|
||||
expectEquals (colour.getGreen(), expectedGreen);
|
||||
expectEquals (colour.getBlue(), expectedBlue);
|
||||
expectEquals (colour.getAlpha(), expectedAlpha);
|
||||
expectEquals (colour.getFloatAlpha(), expectedFloatAlpha);
|
||||
};
|
||||
|
||||
beginTest ("Constructors");
|
||||
{
|
||||
Colour c1;
|
||||
testColour (c1, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
|
||||
|
||||
Colour c2 ((uint32) 0);
|
||||
testColour (c2, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
|
||||
|
||||
Colour c3 ((uint32) 0xffffffff);
|
||||
testColour (c3, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
|
||||
|
||||
Colour c4 (0, 0, 0);
|
||||
testColour (c4, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 255, 1.0f);
|
||||
|
||||
Colour c5 (255, 255, 255);
|
||||
testColour (c5, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
|
||||
|
||||
Colour c6 ((uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0);
|
||||
testColour (c6, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
|
||||
|
||||
Colour c7 ((uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255);
|
||||
testColour (c7, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
|
||||
|
||||
Colour c8 ((uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
|
||||
testColour (c8, (uint8) 0, (uint8) 0, (uint8) 0, (uint8) 0, 0.0f);
|
||||
|
||||
Colour c9 ((uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
|
||||
testColour (c9, (uint8) 255, (uint8) 255, (uint8) 255, (uint8) 255, 1.0f);
|
||||
}
|
||||
|
||||
beginTest ("HSV");
|
||||
{
|
||||
// black
|
||||
testColour (Colour::fromHSV (0.0f, 0.0f, 0.0f, 1.0f), 0, 0, 0);
|
||||
// white
|
||||
testColour (Colour::fromHSV (0.0f, 0.0f, 1.0f, 1.0f), 255, 255, 255);
|
||||
// red
|
||||
testColour (Colour::fromHSV (0.0f, 1.0f, 1.0f, 1.0f), 255, 0, 0);
|
||||
testColour (Colour::fromHSV (1.0f, 1.0f, 1.0f, 1.0f), 255, 0, 0);
|
||||
// lime
|
||||
testColour (Colour::fromHSV (120 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 255, 0);
|
||||
// blue
|
||||
testColour (Colour::fromHSV (240 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 0, 255);
|
||||
// yellow
|
||||
testColour (Colour::fromHSV (60 / 360.0f, 1.0f, 1.0f, 1.0f), 255, 255, 0);
|
||||
// cyan
|
||||
testColour (Colour::fromHSV (180 / 360.0f, 1.0f, 1.0f, 1.0f), 0, 255, 255);
|
||||
// magenta
|
||||
testColour (Colour::fromHSV (300 / 360.0f, 1.0f, 1.0f, 1.0f), 255, 0, 255);
|
||||
// silver
|
||||
testColour (Colour::fromHSV (0.0f, 0.0f, 0.75f, 1.0f), 191, 191, 191);
|
||||
// grey
|
||||
testColour (Colour::fromHSV (0.0f, 0.0f, 0.5f, 1.0f), 128, 128, 128);
|
||||
// maroon
|
||||
testColour (Colour::fromHSV (0.0f, 1.0f, 0.5f, 1.0f), 128, 0, 0);
|
||||
// olive
|
||||
testColour (Colour::fromHSV (60 / 360.0f, 1.0f, 0.5f, 1.0f), 128, 128, 0);
|
||||
// green
|
||||
testColour (Colour::fromHSV (120 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 128, 0);
|
||||
// purple
|
||||
testColour (Colour::fromHSV (300 / 360.0f, 1.0f, 0.5f, 1.0f), 128, 0, 128);
|
||||
// teal
|
||||
testColour (Colour::fromHSV (180 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 128, 128);
|
||||
// navy
|
||||
testColour (Colour::fromHSV (240 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 0, 128);
|
||||
}
|
||||
|
||||
beginTest ("HSL");
|
||||
{
|
||||
// black
|
||||
testColour (Colour::fromHSL (0.0f, 0.0f, 0.0f, 1.0f), 0, 0, 0);
|
||||
// white
|
||||
testColour (Colour::fromHSL (0.0f, 0.0f, 1.0f, 1.0f), 255, 255, 255);
|
||||
// red
|
||||
testColour (Colour::fromHSL (0.0f, 1.0f, 0.5f, 1.0f), 255, 0, 0);
|
||||
testColour (Colour::fromHSL (1.0f, 1.0f, 0.5f, 1.0f), 255, 0, 0);
|
||||
// lime
|
||||
testColour (Colour::fromHSL (120 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 255, 0);
|
||||
// blue
|
||||
testColour (Colour::fromHSL (240 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 0, 255);
|
||||
// yellow
|
||||
testColour (Colour::fromHSL (60 / 360.0f, 1.0f, 0.5f, 1.0f), 255, 255, 0);
|
||||
// cyan
|
||||
testColour (Colour::fromHSL (180 / 360.0f, 1.0f, 0.5f, 1.0f), 0, 255, 255);
|
||||
// magenta
|
||||
testColour (Colour::fromHSL (300 / 360.0f, 1.0f, 0.5f, 1.0f), 255, 0, 255);
|
||||
// silver
|
||||
testColour (Colour::fromHSL (0.0f, 0.0f, 0.75f, 1.0f), 191, 191, 191);
|
||||
// grey
|
||||
testColour (Colour::fromHSL (0.0f, 0.0f, 0.5f, 1.0f), 128, 128, 128);
|
||||
// maroon
|
||||
testColour (Colour::fromHSL (0.0f, 1.0f, 0.25f, 1.0f), 128, 0, 0);
|
||||
// olive
|
||||
testColour (Colour::fromHSL (60 / 360.0f, 1.0f, 0.25f, 1.0f), 128, 128, 0);
|
||||
// green
|
||||
testColour (Colour::fromHSL (120 / 360.0f, 1.0f, 0.25f, 1.0f), 0, 128, 0);
|
||||
// purple
|
||||
testColour (Colour::fromHSL (300 / 360.0f, 1.0f, 0.25f, 1.0f), 128, 0, 128);
|
||||
// teal
|
||||
testColour (Colour::fromHSL (180 / 360.0f, 1.0f, 0.25f, 1.0f), 0, 128, 128);
|
||||
// navy
|
||||
testColour (Colour::fromHSL (240 / 360.0f, 1.0f, 0.25f, 1.0f), 0, 0, 128);
|
||||
}
|
||||
|
||||
beginTest ("Modifiers");
|
||||
{
|
||||
Colour red (255, 0, 0);
|
||||
testColour (red, 255, 0, 0);
|
||||
|
||||
testColour (red.withHue (120.0f / 360.0f), 0, 255, 0);
|
||||
testColour (red.withSaturation (0.5f), 255, 128, 128);
|
||||
testColour (red.withSaturationHSL (0.5f), 191, 64, 64);
|
||||
testColour (red.withBrightness (0.5f), 128, 0, 0);
|
||||
testColour (red.withLightness (1.0f), 255, 255, 255);
|
||||
testColour (red.withRotatedHue (120.0f / 360.0f), 0, 255, 0);
|
||||
testColour (red.withRotatedHue (480.0f / 360.0f), 0, 255, 0);
|
||||
testColour (red.withRotatedHue (-240.0f / 360.0f), 0, 255, 0);
|
||||
testColour (red.withRotatedHue (-600.0f / 360.0f), 0, 255, 0);
|
||||
testColour (red.withMultipliedSaturation (0.0f), 255, 255, 255);
|
||||
testColour (red.withMultipliedSaturationHSL (0.0f), 128, 128, 128);
|
||||
testColour (red.withMultipliedBrightness (0.5f), 128, 0, 0);
|
||||
testColour (red.withMultipliedLightness (2.0f), 255, 255, 255);
|
||||
testColour (red.withMultipliedLightness (1.0f), 255, 0, 0);
|
||||
testColour (red.withLightness (red.getLightness()), 255, 0, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static ColourTests colourTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a colour, also including a transparency value.
|
||||
|
||||
The colour is stored internally as unsigned 8-bit red, green, blue and alpha values.
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API Colour final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a transparent black colour. */
|
||||
Colour() = default;
|
||||
|
||||
/** Creates a copy of another Colour object. */
|
||||
Colour (const Colour&) = default;
|
||||
|
||||
/** Creates a colour from a 32-bit ARGB value.
|
||||
|
||||
The format of this number is:
|
||||
((alpha << 24) | (red << 16) | (green << 8) | blue).
|
||||
|
||||
All components in the range 0x00 to 0xff.
|
||||
An alpha of 0x00 is completely transparent, alpha of 0xff is opaque.
|
||||
|
||||
@see getPixelARGB
|
||||
*/
|
||||
explicit Colour (uint32 argb) noexcept;
|
||||
|
||||
/** Creates an opaque colour using 8-bit red, green and blue values */
|
||||
Colour (uint8 red,
|
||||
uint8 green,
|
||||
uint8 blue) noexcept;
|
||||
|
||||
/** Creates an opaque colour using 8-bit red, green and blue values */
|
||||
static Colour fromRGB (uint8 red,
|
||||
uint8 green,
|
||||
uint8 blue) noexcept;
|
||||
|
||||
/** Creates a colour using 8-bit red, green, blue and alpha values. */
|
||||
Colour (uint8 red,
|
||||
uint8 green,
|
||||
uint8 blue,
|
||||
uint8 alpha) noexcept;
|
||||
|
||||
/** Creates a colour using 8-bit red, green, blue and alpha values. */
|
||||
static Colour fromRGBA (uint8 red,
|
||||
uint8 green,
|
||||
uint8 blue,
|
||||
uint8 alpha) noexcept;
|
||||
|
||||
/** Creates a colour from 8-bit red, green, and blue values, and a floating-point alpha.
|
||||
|
||||
Alpha of 0.0 is transparent, alpha of 1.0f is opaque.
|
||||
Values outside the valid range will be clipped.
|
||||
*/
|
||||
Colour (uint8 red,
|
||||
uint8 green,
|
||||
uint8 blue,
|
||||
float alpha) noexcept;
|
||||
|
||||
/** Creates a colour using floating point red, green, blue and alpha values.
|
||||
Numbers outside the range 0..1 will be clipped.
|
||||
*/
|
||||
static Colour fromFloatRGBA (float red,
|
||||
float green,
|
||||
float blue,
|
||||
float alpha) noexcept;
|
||||
|
||||
/** Creates a colour using floating point hue, saturation and brightness values, and an 8-bit alpha.
|
||||
|
||||
The floating point values must be between 0.0 and 1.0.
|
||||
An alpha of 0x00 is completely transparent, alpha of 0xff is opaque.
|
||||
Values outside the valid range will be clipped.
|
||||
*/
|
||||
Colour (float hue,
|
||||
float saturation,
|
||||
float brightness,
|
||||
uint8 alpha) noexcept;
|
||||
|
||||
/** Creates a colour using floating point hue, saturation, brightness and alpha values.
|
||||
|
||||
All values must be between 0.0 and 1.0.
|
||||
Numbers outside the valid range will be clipped.
|
||||
*/
|
||||
Colour (float hue,
|
||||
float saturation,
|
||||
float brightness,
|
||||
float alpha) noexcept;
|
||||
|
||||
/** Creates a colour using floating point hue, saturation, brightness and alpha values.
|
||||
|
||||
All values must be between 0.0 and 1.0.
|
||||
Numbers outside the valid range will be clipped.
|
||||
*/
|
||||
static Colour fromHSV (float hue,
|
||||
float saturation,
|
||||
float brightness,
|
||||
float alpha) noexcept;
|
||||
|
||||
/** Creates a colour using floating point hue, saturation, lightness and alpha values.
|
||||
|
||||
All values must be between 0.0 and 1.0.
|
||||
Numbers outside the valid range will be clipped.
|
||||
*/
|
||||
static Colour fromHSL (float hue,
|
||||
float saturation,
|
||||
float lightness,
|
||||
float alpha) noexcept;
|
||||
|
||||
/** Creates a colour using a PixelARGB object. This function assumes that the argb pixel is
|
||||
not premultiplied.
|
||||
*/
|
||||
Colour (PixelARGB argb) noexcept;
|
||||
|
||||
/** Creates a colour using a PixelRGB object.
|
||||
*/
|
||||
Colour (PixelRGB rgb) noexcept;
|
||||
|
||||
/** Creates a colour using a PixelAlpha object.
|
||||
*/
|
||||
Colour (PixelAlpha alpha) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~Colour() = default;
|
||||
|
||||
/** Copies another Colour object. */
|
||||
Colour& operator= (const Colour&) = default;
|
||||
|
||||
/** Compares two colours. */
|
||||
bool operator== (const Colour& other) const noexcept;
|
||||
/** Compares two colours. */
|
||||
bool operator!= (const Colour& other) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the red component of this colour.
|
||||
@returns a value between 0x00 and 0xff.
|
||||
*/
|
||||
uint8 getRed() const noexcept { return argb.getRed(); }
|
||||
|
||||
/** Returns the green component of this colour.
|
||||
@returns a value between 0x00 and 0xff.
|
||||
*/
|
||||
uint8 getGreen() const noexcept { return argb.getGreen(); }
|
||||
|
||||
/** Returns the blue component of this colour.
|
||||
@returns a value between 0x00 and 0xff.
|
||||
*/
|
||||
uint8 getBlue() const noexcept { return argb.getBlue(); }
|
||||
|
||||
/** Returns the red component of this colour as a floating point value.
|
||||
@returns a value between 0.0 and 1.0
|
||||
*/
|
||||
float getFloatRed() const noexcept;
|
||||
|
||||
/** Returns the green component of this colour as a floating point value.
|
||||
@returns a value between 0.0 and 1.0
|
||||
*/
|
||||
float getFloatGreen() const noexcept;
|
||||
|
||||
/** Returns the blue component of this colour as a floating point value.
|
||||
@returns a value between 0.0 and 1.0
|
||||
*/
|
||||
float getFloatBlue() const noexcept;
|
||||
|
||||
/** Returns a premultiplied ARGB pixel object that represents this colour.
|
||||
*/
|
||||
PixelARGB getPixelARGB() const noexcept;
|
||||
|
||||
/** Returns an ARGB pixel object that represents this colour.
|
||||
*/
|
||||
PixelARGB getNonPremultipliedPixelARGB() const noexcept;
|
||||
|
||||
/** Returns a 32-bit integer that represents this colour.
|
||||
|
||||
The format of this number is:
|
||||
((alpha << 24) | (red << 16) | (green << 8) | blue).
|
||||
*/
|
||||
uint32 getARGB() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the colour's alpha (opacity).
|
||||
|
||||
Alpha of 0x00 is completely transparent, 0xff is completely opaque.
|
||||
*/
|
||||
uint8 getAlpha() const noexcept { return argb.getAlpha(); }
|
||||
|
||||
/** Returns the colour's alpha (opacity) as a floating point value.
|
||||
|
||||
Alpha of 0.0 is completely transparent, 1.0 is completely opaque.
|
||||
*/
|
||||
float getFloatAlpha() const noexcept;
|
||||
|
||||
/** Returns true if this colour is completely opaque.
|
||||
|
||||
Equivalent to (getAlpha() == 0xff).
|
||||
*/
|
||||
bool isOpaque() const noexcept;
|
||||
|
||||
/** Returns true if this colour is completely transparent.
|
||||
|
||||
Equivalent to (getAlpha() == 0x00).
|
||||
*/
|
||||
bool isTransparent() const noexcept;
|
||||
|
||||
/** Returns a colour that's the same colour as this one, but with a new alpha value. */
|
||||
Colour withAlpha (uint8 newAlpha) const noexcept;
|
||||
|
||||
/** Returns a colour that's the same colour as this one, but with a new alpha value. */
|
||||
Colour withAlpha (float newAlpha) const noexcept;
|
||||
|
||||
/** Returns a colour that's the same colour as this one, but with a modified alpha value.
|
||||
The new colour's alpha will be this object's alpha multiplied by the value passed-in.
|
||||
*/
|
||||
Colour withMultipliedAlpha (float alphaMultiplier) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a colour that is the result of alpha-compositing a new colour over this one.
|
||||
If the foreground colour is semi-transparent, it is blended onto this colour accordingly.
|
||||
*/
|
||||
Colour overlaidWith (Colour foregroundColour) const noexcept;
|
||||
|
||||
/** Returns a colour that lies somewhere between this one and another.
|
||||
If amountOfOther is zero, the result is 100% this colour, if amountOfOther
|
||||
is 1.0, the result is 100% of the other colour.
|
||||
*/
|
||||
Colour interpolatedWith (Colour other, float proportionOfOther) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the colour's hue component.
|
||||
The value returned is in the range 0.0 to 1.0
|
||||
*/
|
||||
float getHue() const noexcept;
|
||||
|
||||
/** Returns the colour's saturation component.
|
||||
The value returned is in the range 0.0 to 1.0
|
||||
*/
|
||||
float getSaturation() const noexcept;
|
||||
|
||||
/** Returns the colour's saturation component as represented in the HSL colour space.
|
||||
The value returned is in the range 0.0 to 1.0
|
||||
*/
|
||||
float getSaturationHSL() const noexcept;
|
||||
|
||||
/** Returns the colour's brightness component.
|
||||
The value returned is in the range 0.0 to 1.0
|
||||
*/
|
||||
float getBrightness() const noexcept;
|
||||
|
||||
/** Returns the colour's lightness component.
|
||||
The value returned is in the range 0.0 to 1.0
|
||||
*/
|
||||
float getLightness() const noexcept;
|
||||
|
||||
/** Returns a skewed brightness value, adjusted to better reflect the way the human
|
||||
eye responds to different colour channels. This makes it better than getBrightness()
|
||||
for comparing differences in brightness.
|
||||
*/
|
||||
float getPerceivedBrightness() const noexcept;
|
||||
|
||||
/** Returns the colour's hue, saturation and brightness components all at once.
|
||||
The values returned are in the range 0.0 to 1.0
|
||||
*/
|
||||
void getHSB (float& hue,
|
||||
float& saturation,
|
||||
float& brightness) const noexcept;
|
||||
|
||||
/** Returns the colour's hue, saturation and lightness components all at once.
|
||||
The values returned are in the range 0.0 to 1.0
|
||||
*/
|
||||
void getHSL (float& hue,
|
||||
float& saturation,
|
||||
float& lightness) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a copy of this colour with a different hue. */
|
||||
[[nodiscard]] Colour withHue (float newHue) const noexcept;
|
||||
|
||||
/** Returns a copy of this colour with a different saturation. */
|
||||
[[nodiscard]] Colour withSaturation (float newSaturation) const noexcept;
|
||||
|
||||
/** Returns a copy of this colour with a different saturation in the HSL colour space. */
|
||||
[[nodiscard]] Colour withSaturationHSL (float newSaturation) const noexcept;
|
||||
|
||||
/** Returns a copy of this colour with a different brightness.
|
||||
@see brighter, darker, withMultipliedBrightness
|
||||
*/
|
||||
[[nodiscard]] Colour withBrightness (float newBrightness) const noexcept;
|
||||
|
||||
/** Returns a copy of this colour with a different lightness.
|
||||
@see lighter, darker, withMultipliedLightness
|
||||
*/
|
||||
[[nodiscard]] Colour withLightness (float newLightness) const noexcept;
|
||||
|
||||
/** Returns a copy of this colour with its hue rotated.
|
||||
The new colour's hue is ((this->getHue() + amountToRotate) % 1.0)
|
||||
@see brighter, darker, withMultipliedBrightness
|
||||
*/
|
||||
[[nodiscard]] Colour withRotatedHue (float amountToRotate) const noexcept;
|
||||
|
||||
/** Returns a copy of this colour with its saturation multiplied by the given value.
|
||||
The new colour's saturation is (this->getSaturation() * multiplier)
|
||||
(the result is clipped to legal limits).
|
||||
*/
|
||||
[[nodiscard]] Colour withMultipliedSaturation (float multiplier) const noexcept;
|
||||
|
||||
/** Returns a copy of this colour with its saturation multiplied by the given value.
|
||||
The new colour's saturation is (this->getSaturation() * multiplier)
|
||||
(the result is clipped to legal limits).
|
||||
|
||||
This will be in the HSL colour space.
|
||||
*/
|
||||
[[nodiscard]] Colour withMultipliedSaturationHSL (float multiplier) const noexcept;
|
||||
|
||||
/** Returns a copy of this colour with its brightness multiplied by the given value.
|
||||
The new colour's brightness is (this->getBrightness() * multiplier)
|
||||
(the result is clipped to legal limits).
|
||||
*/
|
||||
[[nodiscard]] Colour withMultipliedBrightness (float amount) const noexcept;
|
||||
|
||||
/** Returns a copy of this colour with its lightness multiplied by the given value.
|
||||
The new colour's lightness is (this->lightness() * multiplier)
|
||||
(the result is clipped to legal limits).
|
||||
*/
|
||||
[[nodiscard]] Colour withMultipliedLightness (float amount) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a brighter version of this colour.
|
||||
@param amountBrighter how much brighter to make it - a value greater than or equal to 0,
|
||||
where 0 is unchanged, and higher values make it brighter
|
||||
@see withMultipliedBrightness
|
||||
*/
|
||||
[[nodiscard]] Colour brighter (float amountBrighter = 0.4f) const noexcept;
|
||||
|
||||
/** Returns a darker version of this colour.
|
||||
@param amountDarker how much darker to make it - a value greater than or equal to 0,
|
||||
where 0 is unchanged, and higher values make it darker
|
||||
@see withMultipliedBrightness
|
||||
*/
|
||||
[[nodiscard]] Colour darker (float amountDarker = 0.4f) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a colour that will be clearly visible against this colour.
|
||||
|
||||
The amount parameter indicates how contrasting the new colour should
|
||||
be, so e.g. Colours::black.contrasting (0.1f) will return a colour
|
||||
that's just a little bit lighter; Colours::black.contrasting (1.0f) will
|
||||
return white; Colours::white.contrasting (1.0f) will return black, etc.
|
||||
*/
|
||||
[[nodiscard]] Colour contrasting (float amount = 1.0f) const noexcept;
|
||||
|
||||
/** Returns a colour that is as close as possible to a target colour whilst
|
||||
still being in contrast to this one.
|
||||
|
||||
The colour that is returned will be the targetColour, but with its luminosity
|
||||
nudged up or down so that it differs from the luminosity of this colour
|
||||
by at least the amount specified by minLuminosityDiff.
|
||||
*/
|
||||
[[nodiscard]] Colour contrasting (Colour targetColour, float minLuminosityDiff) const noexcept;
|
||||
|
||||
/** Returns a colour that contrasts against two colours.
|
||||
Looks for a colour that contrasts with both of the colours passed-in.
|
||||
Handy for things like choosing a highlight colour in text editors, etc.
|
||||
*/
|
||||
[[nodiscard]] static Colour contrasting (Colour colour1,
|
||||
Colour colour2) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns an opaque shade of grey.
|
||||
@param brightness the level of grey to return - 0 is black, 1.0 is white
|
||||
*/
|
||||
[[nodiscard]] static Colour greyLevel (float brightness) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a stringified version of this colour.
|
||||
The string can be turned back into a colour using the fromString() method.
|
||||
*/
|
||||
String toString() const;
|
||||
|
||||
/** Reads the colour from a string that was created with toString(). */
|
||||
[[nodiscard]] static Colour fromString (StringRef encodedColourString);
|
||||
|
||||
/** Returns the colour as a hex string in the form RRGGBB or AARRGGBB. */
|
||||
String toDisplayString (bool includeAlphaValue) const;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
PixelARGB argb { 0, 0, 0, 0 };
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ColourGradient::ColourGradient() noexcept : isRadial (false)
|
||||
{
|
||||
#if JUCE_DEBUG
|
||||
point1.setX (987654.0f);
|
||||
#define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED jassert (! exactlyEqual (point1.x, 987654.0f));
|
||||
#else
|
||||
#define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED
|
||||
#endif
|
||||
}
|
||||
|
||||
ColourGradient::ColourGradient (const ColourGradient& other)
|
||||
: point1 (other.point1), point2 (other.point2), isRadial (other.isRadial), colours (other.colours)
|
||||
{}
|
||||
|
||||
ColourGradient::ColourGradient (ColourGradient&& other) noexcept
|
||||
: point1 (other.point1), point2 (other.point2), isRadial (other.isRadial),
|
||||
colours (std::move (other.colours))
|
||||
{}
|
||||
|
||||
ColourGradient& ColourGradient::operator= (const ColourGradient& other)
|
||||
{
|
||||
point1 = other.point1;
|
||||
point2 = other.point2;
|
||||
isRadial = other.isRadial;
|
||||
colours = other.colours;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ColourGradient& ColourGradient::operator= (ColourGradient&& other) noexcept
|
||||
{
|
||||
point1 = other.point1;
|
||||
point2 = other.point2;
|
||||
isRadial = other.isRadial;
|
||||
colours = std::move (other.colours);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ColourGradient::ColourGradient (Colour colour1, float x1, float y1,
|
||||
Colour colour2, float x2, float y2, bool radial)
|
||||
: ColourGradient (colour1, Point<float> (x1, y1),
|
||||
colour2, Point<float> (x2, y2), radial)
|
||||
{
|
||||
}
|
||||
|
||||
ColourGradient::ColourGradient (Colour colour1, Point<float> p1,
|
||||
Colour colour2, Point<float> p2, bool radial)
|
||||
: point1 (p1),
|
||||
point2 (p2),
|
||||
isRadial (radial)
|
||||
{
|
||||
colours.add (ColourPoint { 0.0, colour1 },
|
||||
ColourPoint { 1.0, colour2 });
|
||||
}
|
||||
|
||||
ColourGradient ColourGradient::vertical (Colour c1, float y1, Colour c2, float y2)
|
||||
{
|
||||
return { c1, 0, y1, c2, 0, y2, false };
|
||||
}
|
||||
|
||||
ColourGradient ColourGradient::horizontal (Colour c1, float x1, Colour c2, float x2)
|
||||
{
|
||||
return { c1, x1, 0, c2, x2, 0, false };
|
||||
}
|
||||
|
||||
struct PointComparisons
|
||||
{
|
||||
auto tie() const { return std::tie (point->x, point->y); }
|
||||
|
||||
bool operator== (const PointComparisons& other) const { return tie() == other.tie(); }
|
||||
bool operator!= (const PointComparisons& other) const { return tie() != other.tie(); }
|
||||
bool operator< (const PointComparisons& other) const { return tie() < other.tie(); }
|
||||
|
||||
const Point<float>* point = nullptr;
|
||||
};
|
||||
|
||||
struct ColourGradient::ColourPointArrayComparisons
|
||||
{
|
||||
bool operator== (const ColourPointArrayComparisons& other) const { return *array == *other.array; }
|
||||
bool operator!= (const ColourPointArrayComparisons& other) const { return *array != *other.array; }
|
||||
|
||||
bool operator< (const ColourPointArrayComparisons& other) const
|
||||
{
|
||||
return std::lexicographical_compare (array->begin(), array->end(), other.array->begin(), other.array->end());
|
||||
}
|
||||
|
||||
const Array<ColourGradient::ColourPoint>* array = nullptr;
|
||||
};
|
||||
|
||||
auto ColourGradient::tie() const
|
||||
{
|
||||
return std::tuple (PointComparisons { &point1 },
|
||||
PointComparisons { &point2 },
|
||||
isRadial,
|
||||
ColourPointArrayComparisons { &colours });
|
||||
}
|
||||
|
||||
bool ColourGradient::operator== (const ColourGradient& other) const noexcept { return tie() == other.tie(); }
|
||||
bool ColourGradient::operator!= (const ColourGradient& other) const noexcept { return tie() != other.tie(); }
|
||||
|
||||
bool ColourGradient::operator< (const ColourGradient& other) const noexcept { return tie() < other.tie(); }
|
||||
bool ColourGradient::operator<= (const ColourGradient& other) const noexcept { return tie() <= other.tie(); }
|
||||
bool ColourGradient::operator> (const ColourGradient& other) const noexcept { return tie() > other.tie(); }
|
||||
bool ColourGradient::operator>= (const ColourGradient& other) const noexcept { return tie() >= other.tie(); }
|
||||
|
||||
//==============================================================================
|
||||
void ColourGradient::clearColours()
|
||||
{
|
||||
colours.clear();
|
||||
}
|
||||
|
||||
int ColourGradient::addColour (const double proportionAlongGradient, Colour colour)
|
||||
{
|
||||
// must be within the two end-points
|
||||
jassert (proportionAlongGradient >= 0 && proportionAlongGradient <= 1.0);
|
||||
|
||||
if (proportionAlongGradient <= 0)
|
||||
{
|
||||
colours.set (0, { 0.0, colour });
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto pos = jmin (1.0, proportionAlongGradient);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < colours.size(); ++i)
|
||||
if (colours.getReference (i).position > pos)
|
||||
break;
|
||||
|
||||
colours.insert (i, { pos, colour });
|
||||
return i;
|
||||
}
|
||||
|
||||
void ColourGradient::removeColour (int index)
|
||||
{
|
||||
jassert (isPositiveAndBelow (index, colours.size()));
|
||||
colours.remove (index);
|
||||
}
|
||||
|
||||
void ColourGradient::multiplyOpacity (const float multiplier) noexcept
|
||||
{
|
||||
for (auto& c : colours)
|
||||
c.colour = c.colour.withMultipliedAlpha (multiplier);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int ColourGradient::getNumColours() const noexcept
|
||||
{
|
||||
return colours.size();
|
||||
}
|
||||
|
||||
double ColourGradient::getColourPosition (int index) const noexcept
|
||||
{
|
||||
if (isPositiveAndBelow (index, colours.size()))
|
||||
return colours.getReference (index).position;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Colour ColourGradient::getColour (int index) const noexcept
|
||||
{
|
||||
if (isPositiveAndBelow (index, colours.size()))
|
||||
return colours.getReference (index).colour;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void ColourGradient::setColour (int index, Colour newColour) noexcept
|
||||
{
|
||||
if (isPositiveAndBelow (index, colours.size()))
|
||||
colours.getReference (index).colour = newColour;
|
||||
}
|
||||
|
||||
Colour ColourGradient::getColourAtPosition (double position) const noexcept
|
||||
{
|
||||
jassert (approximatelyEqual (colours.getReference (0).position, 0.0)); // the first colour specified has to go at position 0
|
||||
|
||||
if (position <= 0 || colours.size() <= 1)
|
||||
return colours.getReference (0).colour;
|
||||
|
||||
int i = colours.size() - 1;
|
||||
while (position < colours.getReference (i).position)
|
||||
--i;
|
||||
|
||||
auto& p1 = colours.getReference (i);
|
||||
|
||||
if (i >= colours.size() - 1)
|
||||
return p1.colour;
|
||||
|
||||
auto& p2 = colours.getReference (i + 1);
|
||||
|
||||
return p1.colour.interpolatedWith (p2.colour, (float) ((position - p1.position) / (p2.position - p1.position)));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ColourGradient::createLookupTable (PixelARGB* const lookupTable, const int numEntries) const noexcept
|
||||
{
|
||||
JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its coordinates?
|
||||
jassert (colours.size() >= 2);
|
||||
jassert (numEntries > 0);
|
||||
jassert (approximatelyEqual (colours.getReference (0).position, 0.0)); // The first colour specified has to go at position 0
|
||||
|
||||
int index = 0;
|
||||
|
||||
for (int j = 0; j < colours.size() - 1; ++j)
|
||||
{
|
||||
const auto& o = colours.getReference (j + 0);
|
||||
const auto& p = colours.getReference (j + 1);
|
||||
const auto numToDo = roundToInt (p.position * (numEntries - 1)) - index;
|
||||
const auto pix1 = o.colour.getNonPremultipliedPixelARGB();
|
||||
const auto pix2 = p.colour.getNonPremultipliedPixelARGB();
|
||||
|
||||
for (auto i = 0; i < numToDo; ++i)
|
||||
{
|
||||
auto blended = pix1;
|
||||
blended.tween (pix2, (uint32) ((i << 8) / numToDo));
|
||||
blended.premultiply();
|
||||
|
||||
jassert (0 <= index && index < numEntries);
|
||||
lookupTable[index++] = blended;
|
||||
}
|
||||
}
|
||||
|
||||
std::fill (lookupTable + index, lookupTable + numEntries, colours.getLast().colour.getPixelARGB());
|
||||
}
|
||||
|
||||
int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock<PixelARGB>& lookupTable) const
|
||||
{
|
||||
JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its coordinates?
|
||||
jassert (colours.size() >= 2);
|
||||
|
||||
auto numEntries = jlimit (1, jmax (1, (colours.size() - 1) << 8),
|
||||
3 * (int) point1.transformedBy (transform)
|
||||
.getDistanceFrom (point2.transformedBy (transform)));
|
||||
lookupTable.malloc (numEntries);
|
||||
createLookupTable (lookupTable, numEntries);
|
||||
return numEntries;
|
||||
}
|
||||
|
||||
bool ColourGradient::isOpaque() const noexcept
|
||||
{
|
||||
for (auto& c : colours)
|
||||
if (! c.colour.isOpaque())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ColourGradient::isInvisible() const noexcept
|
||||
{
|
||||
for (auto& c : colours)
|
||||
if (! c.colour.isTransparent())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Describes the layout and colours that should be used to paint a colour gradient.
|
||||
|
||||
@see Graphics::setGradientFill
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API ColourGradient final
|
||||
{
|
||||
public:
|
||||
/** Creates an uninitialised gradient.
|
||||
|
||||
If you use this constructor instead of the other one, be sure to set all the
|
||||
object's public member variables before using it!
|
||||
*/
|
||||
ColourGradient() noexcept;
|
||||
|
||||
ColourGradient (const ColourGradient&);
|
||||
ColourGradient (ColourGradient&&) noexcept;
|
||||
ColourGradient& operator= (const ColourGradient&);
|
||||
ColourGradient& operator= (ColourGradient&&) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a gradient object.
|
||||
|
||||
(x1, y1) is the location to draw with colour1. Likewise (x2, y2) is where
|
||||
colour2 should be. In between them there's a gradient.
|
||||
|
||||
If isRadial is true, the colours form a circular gradient with (x1, y1) at
|
||||
its centre.
|
||||
|
||||
The alpha transparencies of the colours are used, so note that
|
||||
if you blend from transparent to a solid colour, the RGB of the transparent
|
||||
colour will become visible in parts of the gradient. e.g. blending
|
||||
from Colour::transparentBlack to Colours::white will produce a
|
||||
muddy grey colour midway, but Colour::transparentWhite to Colours::white
|
||||
will be white all the way across.
|
||||
|
||||
@see ColourGradient
|
||||
*/
|
||||
ColourGradient (Colour colour1, float x1, float y1,
|
||||
Colour colour2, float x2, float y2,
|
||||
bool isRadial);
|
||||
|
||||
/** Creates a gradient object.
|
||||
|
||||
point1 is the location to draw with colour1. Likewise point2 is where
|
||||
colour2 should be. In between them there's a gradient.
|
||||
|
||||
If isRadial is true, the colours form a circular gradient with point1 at
|
||||
its centre.
|
||||
|
||||
The alpha transparencies of the colours are used, so note that
|
||||
if you blend from transparent to a solid colour, the RGB of the transparent
|
||||
colour will become visible in parts of the gradient. e.g. blending
|
||||
from Colour::transparentBlack to Colours::white will produce a
|
||||
muddy grey colour midway, but Colour::transparentWhite to Colours::white
|
||||
will be white all the way across.
|
||||
|
||||
@see ColourGradient
|
||||
*/
|
||||
ColourGradient (Colour colour1, Point<float> point1,
|
||||
Colour colour2, Point<float> point2,
|
||||
bool isRadial);
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a vertical linear gradient between two Y coordinates */
|
||||
static ColourGradient vertical (Colour colour1, float y1,
|
||||
Colour colour2, float y2);
|
||||
|
||||
/** Creates a horizontal linear gradient between two X coordinates */
|
||||
static ColourGradient horizontal (Colour colour1, float x1,
|
||||
Colour colour2, float x2);
|
||||
|
||||
/** Creates a vertical linear gradient from top to bottom in a rectangle */
|
||||
template <typename Type>
|
||||
static ColourGradient vertical (Colour colourTop, Colour colourBottom, Rectangle<Type> area)
|
||||
{
|
||||
return vertical (colourTop, (float) area.getY(), colourBottom, (float) area.getBottom());
|
||||
}
|
||||
|
||||
/** Creates a horizontal linear gradient from right to left in a rectangle */
|
||||
template <typename Type>
|
||||
static ColourGradient horizontal (Colour colourLeft, Colour colourRight, Rectangle<Type> area)
|
||||
{
|
||||
return horizontal (colourLeft, (float) area.getX(), colourRight, (float) area.getRight());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Removes any colours that have been added.
|
||||
|
||||
This will also remove any start and end colours, so the gradient won't work. You'll
|
||||
need to add more colours with addColour().
|
||||
*/
|
||||
void clearColours();
|
||||
|
||||
/** Adds a colour at a point along the length of the gradient.
|
||||
|
||||
This allows the gradient to go through a spectrum of colours, instead of just a
|
||||
start and end colour.
|
||||
|
||||
@param proportionAlongGradient a value between 0 and 1.0, which is the proportion
|
||||
of the distance along the line between the two points
|
||||
at which the colour should occur.
|
||||
@param colour the colour that should be used at this point
|
||||
@returns the index at which the new point was added
|
||||
*/
|
||||
int addColour (double proportionAlongGradient, Colour colour);
|
||||
|
||||
/** Removes one of the colours from the gradient. */
|
||||
void removeColour (int index);
|
||||
|
||||
/** Multiplies the alpha value of all the colours by the given scale factor */
|
||||
void multiplyOpacity (float multiplier) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of colour-stops that have been added. */
|
||||
int getNumColours() const noexcept;
|
||||
|
||||
/** Returns the position along the length of the gradient of the colour with this index.
|
||||
|
||||
The index is from 0 to getNumColours() - 1. The return value will be between 0.0 and 1.0
|
||||
*/
|
||||
double getColourPosition (int index) const noexcept;
|
||||
|
||||
/** Returns the colour that was added with a given index.
|
||||
The index is from 0 to getNumColours() - 1.
|
||||
*/
|
||||
Colour getColour (int index) const noexcept;
|
||||
|
||||
/** Changes the colour at a given index.
|
||||
The index is from 0 to getNumColours() - 1.
|
||||
*/
|
||||
void setColour (int index, Colour newColour) noexcept;
|
||||
|
||||
/** Returns the an interpolated colour at any position along the gradient.
|
||||
@param position the position along the gradient, between 0 and 1
|
||||
*/
|
||||
Colour getColourAtPosition (double position) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a set of interpolated premultiplied ARGB values.
|
||||
This will resize the HeapBlock, fill it with the colours, and will return the number of
|
||||
colours that it added.
|
||||
When calling this, the ColourGradient must have at least 2 colour stops specified.
|
||||
*/
|
||||
int createLookupTable (const AffineTransform& transform, HeapBlock<PixelARGB>& resultLookupTable) const;
|
||||
|
||||
/** Creates a set of interpolated premultiplied ARGB values.
|
||||
This will fill an array of a user-specified size with the gradient, interpolating to fit.
|
||||
The numEntries argument specifies the size of the array, and this size must be greater than zero.
|
||||
When calling this, the ColourGradient must have at least 2 colour stops specified.
|
||||
*/
|
||||
void createLookupTable (PixelARGB* resultLookupTable, int numEntries) const noexcept;
|
||||
|
||||
/** Creates a set of interpolated premultiplied ARGB values.
|
||||
This will fill an array of a user-specified size with the gradient, interpolating to fit.
|
||||
When calling this, the ColourGradient must have at least 2 colour stops specified.
|
||||
*/
|
||||
template <size_t NumEntries>
|
||||
void createLookupTable (PixelARGB (&resultLookupTable)[NumEntries]) const noexcept
|
||||
{
|
||||
static_assert (NumEntries != 0);
|
||||
createLookupTable (resultLookupTable, NumEntries);
|
||||
}
|
||||
|
||||
/** Returns true if all colours are opaque. */
|
||||
bool isOpaque() const noexcept;
|
||||
|
||||
/** Returns true if all colours are completely transparent. */
|
||||
bool isInvisible() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
Point<float> point1, point2;
|
||||
|
||||
/** If true, the gradient should be filled circularly, centred around
|
||||
point1, with point2 defining a point on the circumference.
|
||||
|
||||
If false, the gradient is linear between the two points.
|
||||
*/
|
||||
bool isRadial;
|
||||
|
||||
bool operator== (const ColourGradient&) const noexcept;
|
||||
bool operator!= (const ColourGradient&) const noexcept;
|
||||
|
||||
/** This comparison, and the other ordered comparisons are provided only for compatibility with
|
||||
ordered container types like std::set and std::map.
|
||||
*/
|
||||
bool operator< (const ColourGradient&) const noexcept;
|
||||
bool operator<= (const ColourGradient&) const noexcept;
|
||||
bool operator> (const ColourGradient&) const noexcept;
|
||||
bool operator>= (const ColourGradient&) const noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct ColourPoint
|
||||
{
|
||||
auto tie() const { return std::tuple (position, colour.getPixelARGB().getNativeARGB()); }
|
||||
|
||||
bool operator== (ColourPoint other) const noexcept { return tie() == other.tie(); }
|
||||
bool operator!= (ColourPoint other) const noexcept { return tie() != other.tie(); }
|
||||
bool operator< (ColourPoint other) const noexcept { return tie() < other.tie(); }
|
||||
|
||||
double position;
|
||||
Colour colour;
|
||||
};
|
||||
|
||||
struct ColourPointArrayComparisons;
|
||||
|
||||
auto tie() const;
|
||||
|
||||
Array<ColourPoint> colours;
|
||||
|
||||
JUCE_LEAK_DETECTOR (ColourGradient)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
Colour Colours::findColourForName (const String& colourName,
|
||||
Colour defaultColour)
|
||||
{
|
||||
struct StringHashAndColour { uint32 stringHash, colour; };
|
||||
|
||||
static const StringHashAndColour presets[]
|
||||
{
|
||||
{ 0x05978fff, 0xff000000 }, /* black */
|
||||
{ 0x06bdcc29, 0xffffffff }, /* white */
|
||||
{ 0x002e305a, 0xff0000ff }, /* blue */
|
||||
{ 0x00308adf, 0xff808080 }, /* grey */
|
||||
{ 0x00308a63, 0xff808080 }, /* gray */
|
||||
{ 0x05e0cf03, 0xff008000 }, /* green */
|
||||
{ 0x0001b891, 0xffff0000 }, /* red */
|
||||
{ 0xd43c6474, 0xffffff00 }, /* yellow */
|
||||
{ 0x620886da, 0xfff0f8ff }, /* aliceblue */
|
||||
{ 0x20a2676a, 0xfffaebd7 }, /* antiquewhite */
|
||||
{ 0x002dcebc, 0xff00ffff }, /* aqua */
|
||||
{ 0x46bb5f7e, 0xff7fffd4 }, /* aquamarine */
|
||||
{ 0x0590228f, 0xfff0ffff }, /* azure */
|
||||
{ 0x05947fe4, 0xfff5f5dc }, /* beige */
|
||||
{ 0xad388e35, 0xffffe4c4 }, /* bisque */
|
||||
{ 0x00674f7e, 0xffffebcd }, /* blanchedalmond */
|
||||
{ 0x39129959, 0xff8a2be2 }, /* blueviolet */
|
||||
{ 0x059a8136, 0xffa52a2a }, /* brown */
|
||||
{ 0x89cea8f9, 0xffdeb887 }, /* burlywood */
|
||||
{ 0x0fa260cf, 0xff5f9ea0 }, /* cadetblue */
|
||||
{ 0x6b748956, 0xff7fff00 }, /* chartreuse */
|
||||
{ 0x2903623c, 0xffd2691e }, /* chocolate */
|
||||
{ 0x05a74431, 0xffff7f50 }, /* coral */
|
||||
{ 0x618d42dd, 0xff6495ed }, /* cornflowerblue */
|
||||
{ 0xe4b479fd, 0xfffff8dc }, /* cornsilk */
|
||||
{ 0x3d8c4edf, 0xffdc143c }, /* crimson */
|
||||
{ 0x002ed323, 0xff00ffff }, /* cyan */
|
||||
{ 0x67cc74d0, 0xff00008b }, /* darkblue */
|
||||
{ 0x67cd1799, 0xff008b8b }, /* darkcyan */
|
||||
{ 0x31bbd168, 0xffb8860b }, /* darkgoldenrod */
|
||||
{ 0x67cecf55, 0xff555555 }, /* darkgrey */
|
||||
{ 0x67ceced9, 0xff555555 }, /* darkgray */
|
||||
{ 0x920b194d, 0xff006400 }, /* darkgreen */
|
||||
{ 0x923edd4c, 0xffbdb76b }, /* darkkhaki */
|
||||
{ 0x5c293873, 0xff8b008b }, /* darkmagenta */
|
||||
{ 0x6b6671fe, 0xff556b2f }, /* darkolivegreen */
|
||||
{ 0xbcfd2524, 0xffff8c00 }, /* darkorange */
|
||||
{ 0xbcfdf799, 0xff9932cc }, /* darkorchid */
|
||||
{ 0x55ee0d5b, 0xff8b0000 }, /* darkred */
|
||||
{ 0xc2e5f564, 0xffe9967a }, /* darksalmon */
|
||||
{ 0x61be858a, 0xff8fbc8f }, /* darkseagreen */
|
||||
{ 0xc2b0f2bd, 0xff483d8b }, /* darkslateblue */
|
||||
{ 0xc2b34d42, 0xff2f4f4f }, /* darkslategrey */
|
||||
{ 0xc2b34cc6, 0xff2f4f4f }, /* darkslategray */
|
||||
{ 0x7cf2b06b, 0xff00ced1 }, /* darkturquoise */
|
||||
{ 0xc8769375, 0xff9400d3 }, /* darkviolet */
|
||||
{ 0x25832862, 0xffff1493 }, /* deeppink */
|
||||
{ 0xfcad568f, 0xff00bfff }, /* deepskyblue */
|
||||
{ 0x634c8b67, 0xff696969 }, /* dimgrey */
|
||||
{ 0x634c8aeb, 0xff696969 }, /* dimgray */
|
||||
{ 0x45c1ce55, 0xff1e90ff }, /* dodgerblue */
|
||||
{ 0xef19e3cb, 0xffb22222 }, /* firebrick */
|
||||
{ 0xb852b195, 0xfffffaf0 }, /* floralwhite */
|
||||
{ 0xd086fd06, 0xff228b22 }, /* forestgreen */
|
||||
{ 0xe106b6d7, 0xffff00ff }, /* fuchsia */
|
||||
{ 0x7880d61e, 0xffdcdcdc }, /* gainsboro */
|
||||
{ 0x2018a2fa, 0xfff8f8ff }, /* ghostwhite */
|
||||
{ 0x00308060, 0xffffd700 }, /* gold */
|
||||
{ 0xb3b3bc1e, 0xffdaa520 }, /* goldenrod */
|
||||
{ 0xbab8a537, 0xffadff2f }, /* greenyellow */
|
||||
{ 0xe4cacafb, 0xfff0fff0 }, /* honeydew */
|
||||
{ 0x41892743, 0xffff69b4 }, /* hotpink */
|
||||
{ 0xd5796f1a, 0xffcd5c5c }, /* indianred */
|
||||
{ 0xb969fed2, 0xff4b0082 }, /* indigo */
|
||||
{ 0x05fef6a9, 0xfffffff0 }, /* ivory */
|
||||
{ 0x06149302, 0xfff0e68c }, /* khaki */
|
||||
{ 0xad5a05c7, 0xffe6e6fa }, /* lavender */
|
||||
{ 0x7c4d5b99, 0xfffff0f5 }, /* lavenderblush */
|
||||
{ 0x41cc4377, 0xff7cfc00 }, /* lawngreen */
|
||||
{ 0x195756f0, 0xfffffacd }, /* lemonchiffon */
|
||||
{ 0x28e4ea70, 0xffadd8e6 }, /* lightblue */
|
||||
{ 0xf3c7ccdb, 0xfff08080 }, /* lightcoral */
|
||||
{ 0x28e58d39, 0xffe0ffff }, /* lightcyan */
|
||||
{ 0x21234e3c, 0xfffafad2 }, /* lightgoldenrodyellow */
|
||||
{ 0xf40157ad, 0xff90ee90 }, /* lightgreen */
|
||||
{ 0x28e744f5, 0xffd3d3d3 }, /* lightgrey */
|
||||
{ 0x28e74479, 0xffd3d3d3 }, /* lightgray */
|
||||
{ 0x28eb3b8c, 0xffffb6c1 }, /* lightpink */
|
||||
{ 0x9fb78304, 0xffffa07a }, /* lightsalmon */
|
||||
{ 0x50632b2a, 0xff20b2aa }, /* lightseagreen */
|
||||
{ 0x68fb7b25, 0xff87cefa }, /* lightskyblue */
|
||||
{ 0xa8a35ba2, 0xff778899 }, /* lightslategrey */
|
||||
{ 0xa8a35b26, 0xff778899 }, /* lightslategray */
|
||||
{ 0xa20d484f, 0xffb0c4de }, /* lightsteelblue */
|
||||
{ 0xaa2cf10a, 0xffffffe0 }, /* lightyellow */
|
||||
{ 0x0032afd5, 0xff00ff00 }, /* lime */
|
||||
{ 0x607bbc4e, 0xff32cd32 }, /* limegreen */
|
||||
{ 0x06234efa, 0xfffaf0e6 }, /* linen */
|
||||
{ 0x316858a9, 0xffff00ff }, /* magenta */
|
||||
{ 0xbf8ca470, 0xff800000 }, /* maroon */
|
||||
{ 0xbd58e0b3, 0xff66cdaa }, /* mediumaquamarine */
|
||||
{ 0x967dfd4f, 0xff0000cd }, /* mediumblue */
|
||||
{ 0x056f5c58, 0xffba55d3 }, /* mediumorchid */
|
||||
{ 0x07556b71, 0xff9370db }, /* mediumpurple */
|
||||
{ 0x5369b689, 0xff3cb371 }, /* mediumseagreen */
|
||||
{ 0x066be19e, 0xff7b68ee }, /* mediumslateblue */
|
||||
{ 0x3256b281, 0xff00fa9a }, /* mediumspringgreen */
|
||||
{ 0xc0ad9f4c, 0xff48d1cc }, /* mediumturquoise */
|
||||
{ 0x628e63dd, 0xffc71585 }, /* mediumvioletred */
|
||||
{ 0x168eb32a, 0xff191970 }, /* midnightblue */
|
||||
{ 0x4306b960, 0xfff5fffa }, /* mintcream */
|
||||
{ 0x4cbc0e6b, 0xffffe4e1 }, /* mistyrose */
|
||||
{ 0xd9447d59, 0xffffe4b5 }, /* moccasin */
|
||||
{ 0xe97218a6, 0xffffdead }, /* navajowhite */
|
||||
{ 0x00337bb6, 0xff000080 }, /* navy */
|
||||
{ 0xadd2d33e, 0xfffdf5e6 }, /* oldlace */
|
||||
{ 0x064ee1db, 0xff808000 }, /* olive */
|
||||
{ 0x9e33a98a, 0xff6b8e23 }, /* olivedrab */
|
||||
{ 0xc3de262e, 0xffffa500 }, /* orange */
|
||||
{ 0x58bebba3, 0xffff4500 }, /* orangered */
|
||||
{ 0xc3def8a3, 0xffda70d6 }, /* orchid */
|
||||
{ 0x28cb4834, 0xffeee8aa }, /* palegoldenrod */
|
||||
{ 0x3d9dd619, 0xff98fb98 }, /* palegreen */
|
||||
{ 0x74022737, 0xffafeeee }, /* paleturquoise */
|
||||
{ 0x15e2ebc8, 0xffdb7093 }, /* palevioletred */
|
||||
{ 0x5fd898e2, 0xffffefd5 }, /* papayawhip */
|
||||
{ 0x93e1b776, 0xffffdab9 }, /* peachpuff */
|
||||
{ 0x003472f8, 0xffcd853f }, /* peru */
|
||||
{ 0x00348176, 0xffffc0cb }, /* pink */
|
||||
{ 0x00348d94, 0xffdda0dd }, /* plum */
|
||||
{ 0xd036be93, 0xffb0e0e6 }, /* powderblue */
|
||||
{ 0xc5c507bc, 0xff800080 }, /* purple */
|
||||
{ 0xf381f607, 0xff663399 }, /* rebeccapurple */
|
||||
{ 0xa89d65b3, 0xffbc8f8f }, /* rosybrown */
|
||||
{ 0xbd9413e1, 0xff4169e1 }, /* royalblue */
|
||||
{ 0xf456044f, 0xff8b4513 }, /* saddlebrown */
|
||||
{ 0xc9c6f66e, 0xfffa8072 }, /* salmon */
|
||||
{ 0x0bb131e1, 0xfff4a460 }, /* sandybrown */
|
||||
{ 0x34636c14, 0xff2e8b57 }, /* seagreen */
|
||||
{ 0x3507fb41, 0xfffff5ee }, /* seashell */
|
||||
{ 0xca348772, 0xffa0522d }, /* sienna */
|
||||
{ 0xca37d30d, 0xffc0c0c0 }, /* silver */
|
||||
{ 0x80da74fb, 0xff87ceeb }, /* skyblue */
|
||||
{ 0x44a8dd73, 0xff6a5acd }, /* slateblue */
|
||||
{ 0x44ab37f8, 0xff708090 }, /* slategrey */
|
||||
{ 0x44ab377c, 0xff708090 }, /* slategray */
|
||||
{ 0x0035f183, 0xfffffafa }, /* snow */
|
||||
{ 0xd5440d16, 0xff00ff7f }, /* springgreen */
|
||||
{ 0x3e1524a5, 0xff4682b4 }, /* steelblue */
|
||||
{ 0x0001bfa1, 0xffd2b48c }, /* tan */
|
||||
{ 0x0036425c, 0xff008080 }, /* teal */
|
||||
{ 0xafc8858f, 0xffd8bfd8 }, /* thistle */
|
||||
{ 0xcc41600a, 0xffff6347 }, /* tomato */
|
||||
{ 0xfeea9b21, 0xff40e0d0 }, /* turquoise */
|
||||
{ 0xcf57947f, 0xffee82ee }, /* violet */
|
||||
{ 0x06bdbae7, 0xfff5deb3 }, /* wheat */
|
||||
{ 0x10802ee6, 0xfff5f5f5 }, /* whitesmoke */
|
||||
{ 0xe1b5130f, 0xff9acd32 }, /* yellowgreen */
|
||||
};
|
||||
|
||||
const auto hash = (uint32) colourName.trim().toLowerCase().hashCode();
|
||||
|
||||
for (auto entry : presets)
|
||||
if (entry.stringHash == hash)
|
||||
return Colour (entry.colour);
|
||||
|
||||
return defaultColour;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
/**
|
||||
Contains a set of predefined named colours (mostly standard HTML colours)
|
||||
|
||||
@see Colour
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
namespace juce::Colours
|
||||
{
|
||||
|
||||
const Colour transparentBlack { 0 };
|
||||
const Colour transparentWhite { 0x00ffffff };
|
||||
|
||||
const Colour aliceblue { 0xfff0f8ff };
|
||||
const Colour antiquewhite { 0xfffaebd7 };
|
||||
const Colour aqua { 0xff00ffff };
|
||||
const Colour aquamarine { 0xff7fffd4 };
|
||||
const Colour azure { 0xfff0ffff };
|
||||
const Colour beige { 0xfff5f5dc };
|
||||
const Colour bisque { 0xffffe4c4 };
|
||||
const Colour black { 0xff000000 };
|
||||
const Colour blanchedalmond { 0xffffebcd };
|
||||
const Colour blue { 0xff0000ff };
|
||||
const Colour blueviolet { 0xff8a2be2 };
|
||||
const Colour brown { 0xffa52a2a };
|
||||
const Colour burlywood { 0xffdeb887 };
|
||||
const Colour cadetblue { 0xff5f9ea0 };
|
||||
const Colour chartreuse { 0xff7fff00 };
|
||||
const Colour chocolate { 0xffd2691e };
|
||||
const Colour coral { 0xffff7f50 };
|
||||
const Colour cornflowerblue { 0xff6495ed };
|
||||
const Colour cornsilk { 0xfffff8dc };
|
||||
const Colour crimson { 0xffdc143c };
|
||||
const Colour cyan { 0xff00ffff };
|
||||
const Colour darkblue { 0xff00008b };
|
||||
const Colour darkcyan { 0xff008b8b };
|
||||
const Colour darkgoldenrod { 0xffb8860b };
|
||||
const Colour darkgrey { 0xff555555 };
|
||||
const Colour darkgreen { 0xff006400 };
|
||||
const Colour darkkhaki { 0xffbdb76b };
|
||||
const Colour darkmagenta { 0xff8b008b };
|
||||
const Colour darkolivegreen { 0xff556b2f };
|
||||
const Colour darkorange { 0xffff8c00 };
|
||||
const Colour darkorchid { 0xff9932cc };
|
||||
const Colour darkred { 0xff8b0000 };
|
||||
const Colour darksalmon { 0xffe9967a };
|
||||
const Colour darkseagreen { 0xff8fbc8f };
|
||||
const Colour darkslateblue { 0xff483d8b };
|
||||
const Colour darkslategrey { 0xff2f4f4f };
|
||||
const Colour darkturquoise { 0xff00ced1 };
|
||||
const Colour darkviolet { 0xff9400d3 };
|
||||
const Colour deeppink { 0xffff1493 };
|
||||
const Colour deepskyblue { 0xff00bfff };
|
||||
const Colour dimgrey { 0xff696969 };
|
||||
const Colour dodgerblue { 0xff1e90ff };
|
||||
const Colour firebrick { 0xffb22222 };
|
||||
const Colour floralwhite { 0xfffffaf0 };
|
||||
const Colour forestgreen { 0xff228b22 };
|
||||
const Colour fuchsia { 0xffff00ff };
|
||||
const Colour gainsboro { 0xffdcdcdc };
|
||||
const Colour ghostwhite { 0xfff8f8ff };
|
||||
const Colour gold { 0xffffd700 };
|
||||
const Colour goldenrod { 0xffdaa520 };
|
||||
const Colour grey { 0xff808080 };
|
||||
const Colour green { 0xff008000 };
|
||||
const Colour greenyellow { 0xffadff2f };
|
||||
const Colour honeydew { 0xfff0fff0 };
|
||||
const Colour hotpink { 0xffff69b4 };
|
||||
const Colour indianred { 0xffcd5c5c };
|
||||
const Colour indigo { 0xff4b0082 };
|
||||
const Colour ivory { 0xfffffff0 };
|
||||
const Colour khaki { 0xfff0e68c };
|
||||
const Colour lavender { 0xffe6e6fa };
|
||||
const Colour lavenderblush { 0xfffff0f5 };
|
||||
const Colour lawngreen { 0xff7cfc00 };
|
||||
const Colour lemonchiffon { 0xfffffacd };
|
||||
const Colour lightblue { 0xffadd8e6 };
|
||||
const Colour lightcoral { 0xfff08080 };
|
||||
const Colour lightcyan { 0xffe0ffff };
|
||||
const Colour lightgoldenrodyellow { 0xfffafad2 };
|
||||
const Colour lightgreen { 0xff90ee90 };
|
||||
const Colour lightgrey { 0xffd3d3d3 };
|
||||
const Colour lightpink { 0xffffb6c1 };
|
||||
const Colour lightsalmon { 0xffffa07a };
|
||||
const Colour lightseagreen { 0xff20b2aa };
|
||||
const Colour lightskyblue { 0xff87cefa };
|
||||
const Colour lightslategrey { 0xff778899 };
|
||||
const Colour lightsteelblue { 0xffb0c4de };
|
||||
const Colour lightyellow { 0xffffffe0 };
|
||||
const Colour lime { 0xff00ff00 };
|
||||
const Colour limegreen { 0xff32cd32 };
|
||||
const Colour linen { 0xfffaf0e6 };
|
||||
const Colour magenta { 0xffff00ff };
|
||||
const Colour maroon { 0xff800000 };
|
||||
const Colour mediumaquamarine { 0xff66cdaa };
|
||||
const Colour mediumblue { 0xff0000cd };
|
||||
const Colour mediumorchid { 0xffba55d3 };
|
||||
const Colour mediumpurple { 0xff9370db };
|
||||
const Colour mediumseagreen { 0xff3cb371 };
|
||||
const Colour mediumslateblue { 0xff7b68ee };
|
||||
const Colour mediumspringgreen { 0xff00fa9a };
|
||||
const Colour mediumturquoise { 0xff48d1cc };
|
||||
const Colour mediumvioletred { 0xffc71585 };
|
||||
const Colour midnightblue { 0xff191970 };
|
||||
const Colour mintcream { 0xfff5fffa };
|
||||
const Colour mistyrose { 0xffffe4e1 };
|
||||
const Colour moccasin { 0xffffe4b5 };
|
||||
const Colour navajowhite { 0xffffdead };
|
||||
const Colour navy { 0xff000080 };
|
||||
const Colour oldlace { 0xfffdf5e6 };
|
||||
const Colour olive { 0xff808000 };
|
||||
const Colour olivedrab { 0xff6b8e23 };
|
||||
const Colour orange { 0xffffa500 };
|
||||
const Colour orangered { 0xffff4500 };
|
||||
const Colour orchid { 0xffda70d6 };
|
||||
const Colour palegoldenrod { 0xffeee8aa };
|
||||
const Colour palegreen { 0xff98fb98 };
|
||||
const Colour paleturquoise { 0xffafeeee };
|
||||
const Colour palevioletred { 0xffdb7093 };
|
||||
const Colour papayawhip { 0xffffefd5 };
|
||||
const Colour peachpuff { 0xffffdab9 };
|
||||
const Colour peru { 0xffcd853f };
|
||||
const Colour pink { 0xffffc0cb };
|
||||
const Colour plum { 0xffdda0dd };
|
||||
const Colour powderblue { 0xffb0e0e6 };
|
||||
const Colour purple { 0xff800080 };
|
||||
const Colour rebeccapurple { 0xff663399 };
|
||||
const Colour red { 0xffff0000 };
|
||||
const Colour rosybrown { 0xffbc8f8f };
|
||||
const Colour royalblue { 0xff4169e1 };
|
||||
const Colour saddlebrown { 0xff8b4513 };
|
||||
const Colour salmon { 0xfffa8072 };
|
||||
const Colour sandybrown { 0xfff4a460 };
|
||||
const Colour seagreen { 0xff2e8b57 };
|
||||
const Colour seashell { 0xfffff5ee };
|
||||
const Colour sienna { 0xffa0522d };
|
||||
const Colour silver { 0xffc0c0c0 };
|
||||
const Colour skyblue { 0xff87ceeb };
|
||||
const Colour slateblue { 0xff6a5acd };
|
||||
const Colour slategrey { 0xff708090 };
|
||||
const Colour snow { 0xfffffafa };
|
||||
const Colour springgreen { 0xff00ff7f };
|
||||
const Colour steelblue { 0xff4682b4 };
|
||||
const Colour tan { 0xffd2b48c };
|
||||
const Colour teal { 0xff008080 };
|
||||
const Colour thistle { 0xffd8bfd8 };
|
||||
const Colour tomato { 0xffff6347 };
|
||||
const Colour turquoise { 0xff40e0d0 };
|
||||
const Colour violet { 0xffee82ee };
|
||||
const Colour wheat { 0xfff5deb3 };
|
||||
const Colour white { 0xffffffff };
|
||||
const Colour whitesmoke { 0xfff5f5f5 };
|
||||
const Colour yellow { 0xffffff00 };
|
||||
const Colour yellowgreen { 0xff9acd32 };
|
||||
|
||||
/** Attempts to look up a string in the list of known colour names, and return
|
||||
the appropriate colour.
|
||||
|
||||
A non-case-sensitive search is made of the list of predefined colours, and
|
||||
if a match is found, that colour is returned. If no match is found, the
|
||||
colour passed in as the defaultColour parameter is returned.
|
||||
*/
|
||||
JUCE_API Colour findColourForName (const String& colourName,
|
||||
Colour defaultColour);
|
||||
|
||||
} // namespace juce::Colours
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
FillType::FillType() noexcept
|
||||
: colour (0xff000000)
|
||||
{
|
||||
}
|
||||
|
||||
FillType::FillType (Colour c) noexcept
|
||||
: colour (c)
|
||||
{
|
||||
}
|
||||
|
||||
FillType::FillType (const ColourGradient& g)
|
||||
: colour (0xff000000), gradient (new ColourGradient (g))
|
||||
{
|
||||
}
|
||||
|
||||
FillType::FillType (ColourGradient&& g)
|
||||
: colour (0xff000000), gradient (new ColourGradient (std::move (g)))
|
||||
{
|
||||
}
|
||||
|
||||
FillType::FillType (const Image& im, const AffineTransform& t) noexcept
|
||||
: colour (0xff000000), image (im), transform (t)
|
||||
{
|
||||
}
|
||||
|
||||
FillType::FillType (const FillType& other)
|
||||
: colour (other.colour),
|
||||
gradient (createCopyIfNotNull (other.gradient.get())),
|
||||
image (other.image),
|
||||
transform (other.transform)
|
||||
{
|
||||
}
|
||||
|
||||
FillType& FillType::operator= (const FillType& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
colour = other.colour;
|
||||
gradient.reset (createCopyIfNotNull (other.gradient.get()));
|
||||
image = other.image;
|
||||
transform = other.transform;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
FillType::FillType (FillType&& other) noexcept
|
||||
: colour (other.colour),
|
||||
gradient (std::move (other.gradient)),
|
||||
image (std::move (other.image)),
|
||||
transform (other.transform)
|
||||
{
|
||||
}
|
||||
|
||||
FillType& FillType::operator= (FillType&& other) noexcept
|
||||
{
|
||||
jassert (this != &other); // hopefully the compiler should make this situation impossible!
|
||||
|
||||
colour = other.colour;
|
||||
gradient = std::move (other.gradient);
|
||||
image = std::move (other.image);
|
||||
transform = other.transform;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FillType::~FillType() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
bool FillType::operator== (const FillType& other) const
|
||||
{
|
||||
return colour == other.colour && image == other.image
|
||||
&& transform == other.transform
|
||||
&& (gradient == other.gradient
|
||||
|| (gradient != nullptr && other.gradient != nullptr && *gradient == *other.gradient));
|
||||
}
|
||||
|
||||
bool FillType::operator!= (const FillType& other) const
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
void FillType::setColour (Colour newColour) noexcept
|
||||
{
|
||||
gradient.reset();
|
||||
image = {};
|
||||
colour = newColour;
|
||||
}
|
||||
|
||||
void FillType::setGradient (const ColourGradient& newGradient)
|
||||
{
|
||||
if (gradient != nullptr)
|
||||
{
|
||||
*gradient = newGradient;
|
||||
}
|
||||
else
|
||||
{
|
||||
image = {};
|
||||
gradient.reset (new ColourGradient (newGradient));
|
||||
colour = Colours::black;
|
||||
}
|
||||
}
|
||||
|
||||
void FillType::setTiledImage (const Image& newImage, const AffineTransform& newTransform) noexcept
|
||||
{
|
||||
gradient.reset();
|
||||
image = newImage;
|
||||
transform = newTransform;
|
||||
colour = Colours::black;
|
||||
}
|
||||
|
||||
void FillType::setOpacity (const float newOpacity) noexcept
|
||||
{
|
||||
colour = colour.withAlpha (newOpacity);
|
||||
}
|
||||
|
||||
bool FillType::isInvisible() const noexcept
|
||||
{
|
||||
return colour.isTransparent() || (gradient != nullptr && gradient->isInvisible());
|
||||
}
|
||||
|
||||
FillType FillType::transformed (const AffineTransform& t) const
|
||||
{
|
||||
FillType f (*this);
|
||||
f.transform = f.transform.followedBy (t);
|
||||
return f;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a colour or fill pattern to use for rendering paths.
|
||||
|
||||
This is used by the Graphics and DrawablePath classes as a way to encapsulate
|
||||
a brush type. It can either be a solid colour, a gradient, or a tiled image.
|
||||
|
||||
@see Graphics::setFillType, DrawablePath::setFill
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API FillType final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a default fill type, of solid black. */
|
||||
FillType() noexcept;
|
||||
|
||||
/** Creates a fill type of a solid colour.
|
||||
@see setColour
|
||||
*/
|
||||
FillType (Colour colour) noexcept;
|
||||
|
||||
/** Creates a gradient fill type.
|
||||
@see setGradient
|
||||
*/
|
||||
FillType (const ColourGradient& gradient);
|
||||
|
||||
/** Creates a gradient fill type.
|
||||
@see setGradient
|
||||
*/
|
||||
FillType (ColourGradient&& gradient);
|
||||
|
||||
/** Creates a tiled image fill type. The transform allows you to set the scaling, offset
|
||||
and rotation of the pattern.
|
||||
@see setTiledImage
|
||||
*/
|
||||
FillType (const Image& image, const AffineTransform& transform) noexcept;
|
||||
|
||||
/** Creates a copy of another FillType. */
|
||||
FillType (const FillType&);
|
||||
|
||||
/** Makes a copy of another FillType. */
|
||||
FillType& operator= (const FillType&);
|
||||
|
||||
/** Move constructor */
|
||||
FillType (FillType&&) noexcept;
|
||||
|
||||
/** Move assignment operator */
|
||||
FillType& operator= (FillType&&) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~FillType() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if this is a solid colour fill, and not a gradient or image. */
|
||||
bool isColour() const noexcept { return gradient == nullptr && image.isNull(); }
|
||||
|
||||
/** Returns true if this is a gradient fill. */
|
||||
bool isGradient() const noexcept { return gradient != nullptr; }
|
||||
|
||||
/** Returns true if this is a tiled image pattern fill. */
|
||||
bool isTiledImage() const noexcept { return image.isValid(); }
|
||||
|
||||
/** Turns this object into a solid colour fill.
|
||||
If the object was an image or gradient, those fields will no longer be valid. */
|
||||
void setColour (Colour newColour) noexcept;
|
||||
|
||||
/** Turns this object into a gradient fill. */
|
||||
void setGradient (const ColourGradient& newGradient);
|
||||
|
||||
/** Turns this object into a tiled image fill type. The transform allows you to set
|
||||
the scaling, offset and rotation of the pattern.
|
||||
*/
|
||||
void setTiledImage (const Image& image, const AffineTransform& transform) noexcept;
|
||||
|
||||
/** Changes the opacity that should be used.
|
||||
If the fill is a solid colour, this just changes the opacity of that colour. For
|
||||
gradients and image tiles, it changes the opacity that will be used for them.
|
||||
*/
|
||||
void setOpacity (float newOpacity) noexcept;
|
||||
|
||||
/** Returns the current opacity to be applied to the colour, gradient, or image.
|
||||
@see setOpacity
|
||||
*/
|
||||
float getOpacity() const noexcept { return colour.getFloatAlpha(); }
|
||||
|
||||
/** Returns true if this fill type is completely transparent. */
|
||||
bool isInvisible() const noexcept;
|
||||
|
||||
/** Returns a copy of this fill, adding the specified transform applied to the
|
||||
existing transform.
|
||||
*/
|
||||
FillType transformed (const AffineTransform& transform) const;
|
||||
|
||||
//==============================================================================
|
||||
/** The solid colour being used.
|
||||
|
||||
If the fill type is not a solid colour, the alpha channel of this colour indicates
|
||||
the opacity that should be used for the fill, and the RGB channels are ignored.
|
||||
*/
|
||||
Colour colour;
|
||||
|
||||
/** Returns the gradient that should be used for filling.
|
||||
This will be nullptr if the object is some other type of fill.
|
||||
If a gradient is active, the overall opacity with which it should be applied
|
||||
is indicated by the alpha channel of the colour variable.
|
||||
*/
|
||||
std::unique_ptr<ColourGradient> gradient;
|
||||
|
||||
/** The image that should be used for tiling.
|
||||
If an image fill is active, the overall opacity with which it should be applied
|
||||
is indicated by the alpha channel of the colour variable.
|
||||
*/
|
||||
Image image;
|
||||
|
||||
/** The transform that should be applied to the image or gradient that's being drawn. */
|
||||
AffineTransform transform;
|
||||
|
||||
//==============================================================================
|
||||
bool operator== (const FillType&) const;
|
||||
bool operator!= (const FillType&) const;
|
||||
|
||||
private:
|
||||
JUCE_LEAK_DETECTOR (FillType)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,749 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MSVC
|
||||
#pragma pack (push, 1)
|
||||
#endif
|
||||
|
||||
class PixelRGB;
|
||||
class PixelAlpha;
|
||||
|
||||
inline uint32 maskPixelComponents (uint32 x) noexcept
|
||||
{
|
||||
return (x >> 8) & 0x00ff00ff;
|
||||
}
|
||||
|
||||
inline uint32 clampPixelComponents (uint32 x) noexcept
|
||||
{
|
||||
return (x | (0x01000100 - maskPixelComponents (x))) & 0x00ff00ff;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a 32-bit INTERNAL pixel with premultiplied alpha, and can perform compositing
|
||||
operations with it.
|
||||
|
||||
This is used internally by the imaging classes.
|
||||
|
||||
@see PixelRGB
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API PixelARGB
|
||||
{
|
||||
public:
|
||||
/** Creates a pixel without defining its colour. */
|
||||
PixelARGB() noexcept = default;
|
||||
|
||||
PixelARGB (uint8 a, uint8 r, uint8 g, uint8 b) noexcept
|
||||
{
|
||||
components.b = b;
|
||||
components.g = g;
|
||||
components.r = r;
|
||||
components.a = a;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a uint32 which represents the pixel in a platform dependent format. */
|
||||
forcedinline uint32 getNativeARGB() const noexcept { return internal; }
|
||||
|
||||
/** Returns a uint32 which will be in argb order as if constructed with the following mask operation
|
||||
((alpha << 24) | (red << 16) | (green << 8) | blue). */
|
||||
forcedinline uint32 getInARGBMaskOrder() const noexcept
|
||||
{
|
||||
#if JUCE_ANDROID
|
||||
return (uint32) ((components.a << 24) | (components.r << 16) | (components.g << 8) | (components.b << 0));
|
||||
#else
|
||||
return getNativeARGB();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words,
|
||||
if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/
|
||||
inline uint32 getInARGBMemoryOrder() const noexcept
|
||||
{
|
||||
#if JUCE_BIG_ENDIAN
|
||||
return getInARGBMaskOrder();
|
||||
#else
|
||||
return (uint32) ((components.b << 24) | (components.g << 16) | (components.r << 8) | components.a);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Return channels with an even index and insert zero bytes between them. This is useful for blending
|
||||
operations. The exact channels which are returned is platform dependent. */
|
||||
forcedinline uint32 getEvenBytes() const noexcept { return 0x00ff00ff & internal; }
|
||||
|
||||
/** Return channels with an odd index and insert zero bytes between them. This is useful for blending
|
||||
operations. The exact channels which are returned is platform dependent. */
|
||||
forcedinline uint32 getOddBytes() const noexcept { return 0x00ff00ff & (internal >> 8); }
|
||||
|
||||
//==============================================================================
|
||||
forcedinline uint8 getAlpha() const noexcept { return components.a; }
|
||||
forcedinline uint8 getRed() const noexcept { return components.r; }
|
||||
forcedinline uint8 getGreen() const noexcept { return components.g; }
|
||||
forcedinline uint8 getBlue() const noexcept { return components.b; }
|
||||
|
||||
//==============================================================================
|
||||
/** Copies another pixel colour over this one.
|
||||
|
||||
This doesn't blend it - this colour is simply replaced by the other one.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void set (const Pixel& src) noexcept
|
||||
{
|
||||
internal = src.getNativeARGB();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the pixel's colour from individual components. */
|
||||
void setARGB (uint8 a, uint8 r, uint8 g, uint8 b) noexcept
|
||||
{
|
||||
components.b = b;
|
||||
components.g = g;
|
||||
components.r = r;
|
||||
components.a = a;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Blends another pixel onto this one.
|
||||
|
||||
This takes into account the opacity of the pixel being overlaid, and blends
|
||||
it accordingly.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void blend (const Pixel& src) noexcept
|
||||
{
|
||||
auto rb = src.getEvenBytes();
|
||||
auto ag = src.getOddBytes();
|
||||
|
||||
const auto alpha = 0x100 - (ag >> 16);
|
||||
|
||||
rb += maskPixelComponents (getEvenBytes() * alpha);
|
||||
ag += maskPixelComponents (getOddBytes() * alpha);
|
||||
|
||||
internal = clampPixelComponents (rb) | (clampPixelComponents (ag) << 8);
|
||||
}
|
||||
|
||||
/** Blends another pixel onto this one.
|
||||
|
||||
This takes into account the opacity of the pixel being overlaid, and blends
|
||||
it accordingly.
|
||||
*/
|
||||
forcedinline void blend (PixelRGB src) noexcept;
|
||||
|
||||
|
||||
/** Blends another pixel onto this one, applying an extra multiplier to its opacity.
|
||||
|
||||
The opacity of the pixel being overlaid is scaled by the extraAlpha factor before
|
||||
being used, so this can blend semi-transparently from a PixelRGB argument.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept
|
||||
{
|
||||
auto rb = maskPixelComponents (extraAlpha * src.getEvenBytes());
|
||||
auto ag = maskPixelComponents (extraAlpha * src.getOddBytes());
|
||||
|
||||
const auto alpha = 0x100 - (ag >> 16);
|
||||
|
||||
rb += maskPixelComponents (getEvenBytes() * alpha);
|
||||
ag += maskPixelComponents (getOddBytes() * alpha);
|
||||
|
||||
internal = clampPixelComponents (rb) | (clampPixelComponents (ag) << 8);
|
||||
}
|
||||
|
||||
/** Blends another pixel with this one, creating a colour that is somewhere
|
||||
between the two, as specified by the amount.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void tween (const Pixel& src, uint32 amount) noexcept
|
||||
{
|
||||
auto dEvenBytes = getEvenBytes();
|
||||
dEvenBytes += (((src.getEvenBytes() - dEvenBytes) * amount) >> 8);
|
||||
dEvenBytes &= 0x00ff00ff;
|
||||
|
||||
auto dOddBytes = getOddBytes();
|
||||
dOddBytes += (((src.getOddBytes() - dOddBytes) * amount) >> 8);
|
||||
dOddBytes &= 0x00ff00ff;
|
||||
dOddBytes <<= 8;
|
||||
|
||||
dOddBytes |= dEvenBytes;
|
||||
internal = dOddBytes;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Replaces the colour's alpha value with another one. */
|
||||
forcedinline void setAlpha (uint8 newAlpha) noexcept
|
||||
{
|
||||
components.a = newAlpha;
|
||||
}
|
||||
|
||||
/** Multiplies the colour's alpha value with another one. */
|
||||
forcedinline void multiplyAlpha (int multiplier) noexcept
|
||||
{
|
||||
// increment alpha by 1, so that if multiplier == 255 (full alpha),
|
||||
// this function will not change the values.
|
||||
++multiplier;
|
||||
|
||||
internal = ((((uint32) multiplier) * getOddBytes()) & 0xff00ff00)
|
||||
| (((((uint32) multiplier) * getEvenBytes()) >> 8) & 0x00ff00ff);
|
||||
}
|
||||
|
||||
forcedinline void multiplyAlpha (float multiplier) noexcept
|
||||
{
|
||||
multiplyAlpha ((int) (multiplier * 255.0f));
|
||||
}
|
||||
|
||||
|
||||
inline PixelARGB getUnpremultiplied() const noexcept
|
||||
{
|
||||
PixelARGB p (internal);
|
||||
p.unpremultiply();
|
||||
return p;
|
||||
}
|
||||
|
||||
/** Premultiplies the pixel's RGB values by its alpha. */
|
||||
forcedinline void premultiply() noexcept
|
||||
{
|
||||
const auto alpha = components.a;
|
||||
|
||||
if (alpha < 0xff)
|
||||
{
|
||||
if (alpha == 0)
|
||||
{
|
||||
components.b = 0;
|
||||
components.g = 0;
|
||||
components.r = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
components.b = (uint8) ((components.b * alpha + 0x7f) >> 8);
|
||||
components.g = (uint8) ((components.g * alpha + 0x7f) >> 8);
|
||||
components.r = (uint8) ((components.r * alpha + 0x7f) >> 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Unpremultiplies the pixel's RGB values. */
|
||||
forcedinline void unpremultiply() noexcept
|
||||
{
|
||||
const auto alpha = components.a;
|
||||
|
||||
if (alpha < 0xff)
|
||||
{
|
||||
if (alpha == 0)
|
||||
{
|
||||
components.b = 0;
|
||||
components.g = 0;
|
||||
components.r = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
components.b = (uint8) jmin ((uint32) 0xffu, (components.b * 0xffu) / alpha);
|
||||
components.g = (uint8) jmin ((uint32) 0xffu, (components.g * 0xffu) / alpha);
|
||||
components.r = (uint8) jmin ((uint32) 0xffu, (components.r * 0xffu) / alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forcedinline void desaturate() noexcept
|
||||
{
|
||||
if (components.a < 0xff && components.a > 0)
|
||||
{
|
||||
const auto newUnpremultipliedLevel = (0xff * ((int) components.r + (int) components.g + (int) components.b) / (3 * components.a));
|
||||
|
||||
components.r = components.g = components.b
|
||||
= (uint8) ((newUnpremultipliedLevel * components.a + 0x7f) >> 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
components.r = components.g = components.b
|
||||
= (uint8) (((int) components.r + (int) components.g + (int) components.b) / 3);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** The indexes of the different components in the byte layout of this type of colour. */
|
||||
#if JUCE_ANDROID
|
||||
#if JUCE_BIG_ENDIAN
|
||||
enum { indexA = 0, indexR = 3, indexG = 2, indexB = 1 };
|
||||
#else
|
||||
enum { indexA = 3, indexR = 0, indexG = 1, indexB = 2 };
|
||||
#endif
|
||||
#else
|
||||
#if JUCE_BIG_ENDIAN
|
||||
enum { indexA = 0, indexR = 1, indexG = 2, indexB = 3 };
|
||||
#else
|
||||
enum { indexA = 3, indexR = 2, indexG = 1, indexB = 0 };
|
||||
#endif
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
PixelARGB (uint32 internalValue) noexcept
|
||||
: internal (internalValue)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct Components
|
||||
{
|
||||
#if JUCE_ANDROID
|
||||
#if JUCE_BIG_ENDIAN
|
||||
uint8 a, b, g, r;
|
||||
#else
|
||||
uint8 r, g, b, a;
|
||||
#endif
|
||||
#else
|
||||
#if JUCE_BIG_ENDIAN
|
||||
uint8 a, r, g, b;
|
||||
#else
|
||||
uint8 b, g, r, a;
|
||||
#endif
|
||||
#endif
|
||||
} JUCE_PACKED;
|
||||
|
||||
union
|
||||
{
|
||||
uint32 internal;
|
||||
Components components;
|
||||
};
|
||||
}
|
||||
#ifndef DOXYGEN
|
||||
JUCE_PACKED
|
||||
#endif
|
||||
;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents a 24-bit RGB pixel, and can perform compositing operations on it.
|
||||
|
||||
This is used internally by the imaging classes.
|
||||
|
||||
@see PixelARGB
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API PixelRGB
|
||||
{
|
||||
public:
|
||||
/** Creates a pixel without defining its colour. */
|
||||
PixelRGB() noexcept = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a uint32 which represents the pixel in a platform dependent format which is compatible
|
||||
with the native format of a PixelARGB.
|
||||
|
||||
@see PixelARGB::getNativeARGB */
|
||||
forcedinline uint32 getNativeARGB() const noexcept
|
||||
{
|
||||
#if JUCE_ANDROID
|
||||
return (uint32) ((0xffu << 24) | r | ((uint32) g << 8) | ((uint32) b << 16));
|
||||
#else
|
||||
return (uint32) ((0xffu << 24) | b | ((uint32) g << 8) | ((uint32) r << 16));
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Returns a uint32 which will be in argb order as if constructed with the following mask operation
|
||||
((alpha << 24) | (red << 16) | (green << 8) | blue). */
|
||||
forcedinline uint32 getInARGBMaskOrder() const noexcept
|
||||
{
|
||||
#if JUCE_ANDROID
|
||||
return (uint32) ((0xffu << 24) | b | ((uint32) g << 8) | ((uint32) r << 16));
|
||||
#else
|
||||
return getNativeARGB();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words,
|
||||
if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/
|
||||
inline uint32 getInARGBMemoryOrder() const noexcept
|
||||
{
|
||||
#if JUCE_BIG_ENDIAN
|
||||
return getInARGBMaskOrder();
|
||||
#else
|
||||
return (uint32) ((b << 24) | (g << 16) | (r << 8) | 0xff);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Return channels with an even index and insert zero bytes between them. This is useful for blending
|
||||
operations. The exact channels which are returned is platform dependent but compatible with the
|
||||
return value of getEvenBytes of the PixelARGB class.
|
||||
|
||||
@see PixelARGB::getEvenBytes */
|
||||
forcedinline uint32 getEvenBytes() const noexcept
|
||||
{
|
||||
#if JUCE_ANDROID
|
||||
return (uint32) (r | (b << 16));
|
||||
#else
|
||||
return (uint32) (b | (r << 16));
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Return channels with an odd index and insert zero bytes between them. This is useful for blending
|
||||
operations. The exact channels which are returned is platform dependent but compatible with the
|
||||
return value of getOddBytes of the PixelARGB class.
|
||||
|
||||
@see PixelARGB::getOddBytes */
|
||||
forcedinline uint32 getOddBytes() const noexcept { return (uint32) 0xff0000 | g; }
|
||||
|
||||
//==============================================================================
|
||||
forcedinline uint8 getAlpha() const noexcept { return 0xff; }
|
||||
forcedinline uint8 getRed() const noexcept { return r; }
|
||||
forcedinline uint8 getGreen() const noexcept { return g; }
|
||||
forcedinline uint8 getBlue() const noexcept { return b; }
|
||||
|
||||
//==============================================================================
|
||||
/** Copies another pixel colour over this one.
|
||||
|
||||
This doesn't blend it - this colour is simply replaced by the other one.
|
||||
Because PixelRGB has no alpha channel, any alpha value in the source pixel
|
||||
is thrown away.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void set (const Pixel& src) noexcept
|
||||
{
|
||||
b = src.getBlue();
|
||||
g = src.getGreen();
|
||||
r = src.getRed();
|
||||
}
|
||||
|
||||
/** Sets the pixel's colour from individual components. */
|
||||
void setARGB (uint8, uint8 red, uint8 green, uint8 blue) noexcept
|
||||
{
|
||||
r = red;
|
||||
g = green;
|
||||
b = blue;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Blends another pixel onto this one.
|
||||
|
||||
This takes into account the opacity of the pixel being overlaid, and blends
|
||||
it accordingly.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void blend (const Pixel& src) noexcept
|
||||
{
|
||||
const auto alpha = (uint32) (0x100 - src.getAlpha());
|
||||
|
||||
// getEvenBytes returns 0x00rr00bb on non-android
|
||||
const auto rb = clampPixelComponents (src.getEvenBytes() + maskPixelComponents (getEvenBytes() * alpha));
|
||||
// getOddBytes returns 0x00aa00gg on non-android
|
||||
const auto ag = clampPixelComponents (src.getOddBytes() + ((g * alpha) >> 8));
|
||||
|
||||
g = (uint8) (ag & 0xff);
|
||||
|
||||
#if JUCE_ANDROID
|
||||
b = (uint8) (rb >> 16);
|
||||
r = (uint8) (rb & 0xff);
|
||||
#else
|
||||
r = (uint8) (rb >> 16);
|
||||
b = (uint8) (rb & 0xff);
|
||||
#endif
|
||||
}
|
||||
|
||||
forcedinline void blend (PixelRGB src) noexcept
|
||||
{
|
||||
set (src);
|
||||
}
|
||||
|
||||
/** Blends another pixel onto this one, applying an extra multiplier to its opacity.
|
||||
|
||||
The opacity of the pixel being overlaid is scaled by the extraAlpha factor before
|
||||
being used, so this can blend semi-transparently from a PixelRGB argument.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept
|
||||
{
|
||||
auto ag = maskPixelComponents (extraAlpha * src.getOddBytes());
|
||||
auto rb = maskPixelComponents (extraAlpha * src.getEvenBytes());
|
||||
|
||||
const auto alpha = 0x100 - (ag >> 16);
|
||||
|
||||
ag = clampPixelComponents (ag + (g * alpha >> 8));
|
||||
rb = clampPixelComponents (rb + maskPixelComponents (getEvenBytes() * alpha));
|
||||
|
||||
g = (uint8) (ag & 0xff);
|
||||
|
||||
#if JUCE_ANDROID
|
||||
b = (uint8) (rb >> 16);
|
||||
r = (uint8) (rb & 0xff);
|
||||
#else
|
||||
r = (uint8) (rb >> 16);
|
||||
b = (uint8) (rb & 0xff);
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Blends another pixel with this one, creating a colour that is somewhere
|
||||
between the two, as specified by the amount.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void tween (const Pixel& src, uint32 amount) noexcept
|
||||
{
|
||||
auto dEvenBytes = getEvenBytes();
|
||||
dEvenBytes += (((src.getEvenBytes() - dEvenBytes) * amount) >> 8);
|
||||
|
||||
auto dOddBytes = getOddBytes();
|
||||
dOddBytes += (((src.getOddBytes() - dOddBytes) * amount) >> 8);
|
||||
|
||||
g = (uint8) (dOddBytes & 0xff); // dOddBytes = 0x00aa00gg
|
||||
|
||||
#if JUCE_ANDROID
|
||||
r = (uint8) (dEvenBytes & 0xff); // dEvenBytes = 0x00bb00rr
|
||||
b = (uint8) (dEvenBytes >> 16);
|
||||
#else
|
||||
b = (uint8) (dEvenBytes & 0xff); // dEvenBytes = 0x00rr00bb
|
||||
r = (uint8) (dEvenBytes >> 16);
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** This method is included for compatibility with the PixelARGB class. */
|
||||
forcedinline void setAlpha (uint8) noexcept {}
|
||||
|
||||
/** Multiplies the colour's alpha value with another one. */
|
||||
forcedinline void multiplyAlpha (int) noexcept {}
|
||||
|
||||
/** Multiplies the colour's alpha value with another one. */
|
||||
forcedinline void multiplyAlpha (float) noexcept {}
|
||||
|
||||
/** Premultiplies the pixel's RGB values by its alpha. */
|
||||
forcedinline void premultiply() noexcept {}
|
||||
|
||||
/** Unpremultiplies the pixel's RGB values. */
|
||||
forcedinline void unpremultiply() noexcept {}
|
||||
|
||||
forcedinline void desaturate() noexcept
|
||||
{
|
||||
r = g = b = (uint8) (((int) r + (int) g + (int) b) / 3);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** The indexes of the different components in the byte layout of this type of colour. */
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
enum { indexR = 0, indexG = 1, indexB = 2 };
|
||||
#else
|
||||
enum { indexR = 2, indexG = 1, indexB = 0 };
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
PixelRGB (uint32 internal) noexcept
|
||||
{
|
||||
#if JUCE_ANDROID
|
||||
b = (uint8) (internal >> 16);
|
||||
g = (uint8) (internal >> 8);
|
||||
r = (uint8) (internal);
|
||||
#else
|
||||
r = (uint8) (internal >> 16);
|
||||
g = (uint8) (internal >> 8);
|
||||
b = (uint8) (internal);
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MAC || JUCE_IOS
|
||||
uint8 r, g, b;
|
||||
#else
|
||||
uint8 b, g, r;
|
||||
#endif
|
||||
|
||||
}
|
||||
#ifndef DOXYGEN
|
||||
JUCE_PACKED
|
||||
#endif
|
||||
;
|
||||
|
||||
forcedinline void PixelARGB::blend (PixelRGB src) noexcept
|
||||
{
|
||||
set (src);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Represents an 8-bit single-channel pixel, and can perform compositing operations on it.
|
||||
|
||||
This is used internally by the imaging classes.
|
||||
|
||||
@see PixelARGB, PixelRGB
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API PixelAlpha
|
||||
{
|
||||
public:
|
||||
/** Creates a pixel without defining its colour. */
|
||||
PixelAlpha() noexcept = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a uint32 which represents the pixel in a platform dependent format which is compatible
|
||||
with the native format of a PixelARGB.
|
||||
|
||||
@see PixelARGB::getNativeARGB */
|
||||
forcedinline uint32 getNativeARGB() const noexcept { return (uint32) ((a << 24) | (a << 16) | (a << 8) | a); }
|
||||
|
||||
/** Returns a uint32 which will be in argb order as if constructed with the following mask operation
|
||||
((alpha << 24) | (red << 16) | (green << 8) | blue). */
|
||||
forcedinline uint32 getInARGBMaskOrder() const noexcept { return getNativeARGB(); }
|
||||
|
||||
/** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words,
|
||||
if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/
|
||||
inline uint32 getInARGBMemoryOrder() const noexcept { return getNativeARGB(); }
|
||||
|
||||
/** Return channels with an even index and insert zero bytes between them. This is useful for blending
|
||||
operations. The exact channels which are returned is platform dependent but compatible with the
|
||||
return value of getEvenBytes of the PixelARGB class.
|
||||
|
||||
@see PixelARGB::getEvenBytes */
|
||||
forcedinline uint32 getEvenBytes() const noexcept { return (uint32) ((a << 16) | a); }
|
||||
|
||||
/** Return channels with an odd index and insert zero bytes between them. This is useful for blending
|
||||
operations. The exact channels which are returned is platform dependent but compatible with the
|
||||
return value of getOddBytes of the PixelARGB class.
|
||||
|
||||
@see PixelARGB::getOddBytes */
|
||||
forcedinline uint32 getOddBytes() const noexcept { return (uint32) ((a << 16) | a); }
|
||||
|
||||
//==============================================================================
|
||||
forcedinline uint8 getAlpha() const noexcept { return a; }
|
||||
forcedinline uint8 getRed() const noexcept { return 0; }
|
||||
forcedinline uint8 getGreen() const noexcept { return 0; }
|
||||
forcedinline uint8 getBlue() const noexcept { return 0; }
|
||||
|
||||
//==============================================================================
|
||||
/** Copies another pixel colour over this one.
|
||||
|
||||
This doesn't blend it - this colour is simply replaced by the other one.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void set (const Pixel& src) noexcept
|
||||
{
|
||||
a = src.getAlpha();
|
||||
}
|
||||
|
||||
/** Sets the pixel's colour from individual components. */
|
||||
forcedinline void setARGB (uint8 a_, uint8, uint8, uint8) noexcept
|
||||
{
|
||||
a = a_;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Blends another pixel onto this one.
|
||||
|
||||
This takes into account the opacity of the pixel being overlaid, and blends
|
||||
it accordingly.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void blend (const Pixel& src) noexcept
|
||||
{
|
||||
const auto srcA = src.getAlpha();
|
||||
a = (uint8) ((a * (0x100 - srcA) >> 8) + srcA);
|
||||
}
|
||||
|
||||
/** Blends another pixel onto this one, applying an extra multiplier to its opacity.
|
||||
|
||||
The opacity of the pixel being overlaid is scaled by the extraAlpha factor before
|
||||
being used, so this can blend semi-transparently from a PixelRGB argument.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept
|
||||
{
|
||||
++extraAlpha;
|
||||
const auto srcAlpha = (int) ((extraAlpha * src.getAlpha()) >> 8);
|
||||
a = (uint8) ((a * (0x100 - srcAlpha) >> 8) + srcAlpha);
|
||||
}
|
||||
|
||||
/** Blends another pixel with this one, creating a colour that is somewhere
|
||||
between the two, as specified by the amount.
|
||||
*/
|
||||
template <class Pixel>
|
||||
forcedinline void tween (const Pixel& src, uint32 amount) noexcept
|
||||
{
|
||||
a += (uint8) (((src.getAlpha() - a) * amount) >> 8);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Replaces the colour's alpha value with another one. */
|
||||
forcedinline void setAlpha (uint8 newAlpha) noexcept
|
||||
{
|
||||
a = newAlpha;
|
||||
}
|
||||
|
||||
/** Multiplies the colour's alpha value with another one. */
|
||||
forcedinline void multiplyAlpha (int multiplier) noexcept
|
||||
{
|
||||
++multiplier;
|
||||
a = (uint8) ((a * multiplier) >> 8);
|
||||
}
|
||||
|
||||
forcedinline void multiplyAlpha (float multiplier) noexcept
|
||||
{
|
||||
a = (uint8) (a * multiplier);
|
||||
}
|
||||
|
||||
/** Premultiplies the pixel's RGB values by its alpha. */
|
||||
forcedinline void premultiply() noexcept {}
|
||||
|
||||
/** Unpremultiplies the pixel's RGB values. */
|
||||
forcedinline void unpremultiply() noexcept {}
|
||||
|
||||
forcedinline void desaturate() noexcept {}
|
||||
|
||||
//==============================================================================
|
||||
/** The indexes of the different components in the byte layout of this type of colour. */
|
||||
enum { indexA = 0 };
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
PixelAlpha (uint32 internal) noexcept
|
||||
: a ((uint8) (internal >> 24)) { }
|
||||
|
||||
//==============================================================================
|
||||
uint8 a;
|
||||
}
|
||||
#ifndef DOXYGEN
|
||||
JUCE_PACKED
|
||||
#endif
|
||||
;
|
||||
|
||||
#if JUCE_MSVC
|
||||
#pragma pack (pop)
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,946 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static auto operator< (const Font& a, const Font& b)
|
||||
{
|
||||
return GraphicsFontHelpers::compareFont (a, b);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static auto operator< (const Rectangle<T>& a, const Rectangle<T>& b)
|
||||
{
|
||||
const auto tie = [] (auto& t) { return std::make_tuple (t.getX(), t.getY(), t.getWidth(), t.getHeight()); };
|
||||
return tie (a) < tie (b);
|
||||
}
|
||||
|
||||
static auto operator< (const Justification& a, const Justification& b)
|
||||
{
|
||||
return a.getFlags() < b.getFlags();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace
|
||||
{
|
||||
template <typename ArrangementArgs>
|
||||
class GlyphArrangementCache final : public DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
GlyphArrangementCache() = default;
|
||||
|
||||
~GlyphArrangementCache() override
|
||||
{
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
template <typename ConfigureArrangement>
|
||||
[[nodiscard]] auto get (ArrangementArgs&& args, ConfigureArrangement&& configureArrangement)
|
||||
{
|
||||
const ScopedTryLock stl (lock);
|
||||
return stl.isLocked() ? cache.get (std::forward<ArrangementArgs> (args), std::forward<ConfigureArrangement> (configureArrangement))
|
||||
: configureArrangement (args);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_SINGLETON_INLINE (GlyphArrangementCache<ArrangementArgs>, false)
|
||||
|
||||
private:
|
||||
LruCache<ArrangementArgs, GlyphArrangement> cache;
|
||||
CriticalSection lock;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
Rectangle<Type> coordsToRectangle (Type x, Type y, Type w, Type h) noexcept
|
||||
{
|
||||
#if JUCE_DEBUG
|
||||
constexpr int maxVal = 0x3fffffff;
|
||||
|
||||
jassertquiet ((int) x >= -maxVal && (int) x <= maxVal
|
||||
&& (int) y >= -maxVal && (int) y <= maxVal
|
||||
&& (int) w >= 0 && (int) w <= maxVal
|
||||
&& (int) h >= 0 && (int) h <= maxVal);
|
||||
#endif
|
||||
|
||||
return { x, y, w, h };
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Graphics::Graphics (const Image& imageToDrawOnto)
|
||||
: contextHolder (imageToDrawOnto.createLowLevelContext()),
|
||||
context (*contextHolder)
|
||||
{
|
||||
jassert (imageToDrawOnto.isValid()); // Can't draw into a null image!
|
||||
}
|
||||
|
||||
Graphics::Graphics (LowLevelGraphicsContext& internalContext) noexcept
|
||||
: context (internalContext)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Graphics::resetToDefaultState()
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::resetToDefaultState, etw::graphicsKeyword, context.getFrameId());
|
||||
|
||||
saveStateIfPending();
|
||||
context.setFill (FillType());
|
||||
context.setFont (FontOptions{}.withMetricsKind (TypefaceMetricsKind::legacy));
|
||||
context.setInterpolationQuality (Graphics::mediumResamplingQuality);
|
||||
}
|
||||
|
||||
bool Graphics::isVectorDevice() const
|
||||
{
|
||||
return context.isVectorDevice();
|
||||
}
|
||||
|
||||
bool Graphics::reduceClipRegion (Rectangle<int> area)
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::reduceClipRegionRectangle, etw::graphicsKeyword, context.getFrameId(), area)
|
||||
|
||||
saveStateIfPending();
|
||||
return context.clipToRectangle (area);
|
||||
}
|
||||
|
||||
bool Graphics::reduceClipRegion (int x, int y, int w, int h)
|
||||
{
|
||||
return reduceClipRegion (coordsToRectangle (x, y, w, h));
|
||||
}
|
||||
|
||||
bool Graphics::reduceClipRegion (const RectangleList<int>& clipRegion)
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::reduceClipRegionRectangleList, etw::graphicsKeyword, context.getFrameId(), clipRegion)
|
||||
|
||||
saveStateIfPending();
|
||||
return context.clipToRectangleList (clipRegion);
|
||||
}
|
||||
|
||||
bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform)
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::reduceClipRegionPath, etw::graphicsKeyword, context.getFrameId());
|
||||
|
||||
saveStateIfPending();
|
||||
context.clipToPath (path, transform);
|
||||
return ! context.isClipEmpty();
|
||||
}
|
||||
|
||||
bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& transform)
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::reduceClipRegionImage, etw::graphicsKeyword, context.getFrameId());
|
||||
|
||||
saveStateIfPending();
|
||||
context.clipToImageAlpha (image, transform);
|
||||
return ! context.isClipEmpty();
|
||||
}
|
||||
|
||||
void Graphics::excludeClipRegion (Rectangle<int> rectangleToExclude)
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::excludeClipRegion, etw::graphicsKeyword, context.getFrameId(), rectangleToExclude);
|
||||
|
||||
saveStateIfPending();
|
||||
context.excludeClipRectangle (rectangleToExclude);
|
||||
}
|
||||
|
||||
bool Graphics::isClipEmpty() const
|
||||
{
|
||||
return context.isClipEmpty();
|
||||
}
|
||||
|
||||
Rectangle<int> Graphics::getClipBounds() const
|
||||
{
|
||||
return context.getClipBounds();
|
||||
}
|
||||
|
||||
void Graphics::saveState()
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::saveState, etw::graphicsKeyword, context.getFrameId());
|
||||
|
||||
saveStateIfPending();
|
||||
saveStatePending = true;
|
||||
}
|
||||
|
||||
void Graphics::restoreState()
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::restoreState, etw::graphicsKeyword, context.getFrameId());
|
||||
|
||||
if (saveStatePending)
|
||||
saveStatePending = false;
|
||||
else
|
||||
context.restoreState();
|
||||
}
|
||||
|
||||
void Graphics::saveStateIfPending()
|
||||
{
|
||||
if (saveStatePending)
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::saveState, etw::graphicsKeyword, context.getFrameId());
|
||||
|
||||
saveStatePending = false;
|
||||
context.saveState();
|
||||
}
|
||||
}
|
||||
|
||||
void Graphics::setOrigin (Point<int> newOrigin)
|
||||
{
|
||||
saveStateIfPending();
|
||||
context.setOrigin (newOrigin);
|
||||
}
|
||||
|
||||
void Graphics::setOrigin (int x, int y)
|
||||
{
|
||||
setOrigin ({ x, y });
|
||||
}
|
||||
|
||||
void Graphics::addTransform (const AffineTransform& transform)
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::addTransform, etw::graphicsKeyword, context.getFrameId());
|
||||
|
||||
saveStateIfPending();
|
||||
context.addTransform (transform);
|
||||
}
|
||||
|
||||
bool Graphics::clipRegionIntersects (Rectangle<int> area) const
|
||||
{
|
||||
return context.clipRegionIntersects (area);
|
||||
}
|
||||
|
||||
void Graphics::beginTransparencyLayer (float layerOpacity)
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::beginTransparencyLayer, etw::graphicsKeyword, context.getFrameId());
|
||||
|
||||
saveStateIfPending();
|
||||
context.beginTransparencyLayer (layerOpacity);
|
||||
}
|
||||
|
||||
void Graphics::endTransparencyLayer()
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::endTransparencyLayer, etw::graphicsKeyword, context.getFrameId());
|
||||
|
||||
context.endTransparencyLayer();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Graphics::setColour (Colour newColour)
|
||||
{
|
||||
saveStateIfPending();
|
||||
context.setFill (newColour);
|
||||
}
|
||||
|
||||
void Graphics::setOpacity (float newOpacity)
|
||||
{
|
||||
saveStateIfPending();
|
||||
context.setOpacity (newOpacity);
|
||||
}
|
||||
|
||||
void Graphics::setGradientFill (const ColourGradient& gradient)
|
||||
{
|
||||
setFillType (gradient);
|
||||
}
|
||||
|
||||
void Graphics::setGradientFill (ColourGradient&& gradient)
|
||||
{
|
||||
setFillType (std::move (gradient));
|
||||
}
|
||||
|
||||
void Graphics::setTiledImageFill (const Image& imageToUse, const int anchorX, const int anchorY, const float opacity)
|
||||
{
|
||||
saveStateIfPending();
|
||||
context.setFill (FillType (imageToUse, AffineTransform::translation ((float) anchorX, (float) anchorY)));
|
||||
context.setOpacity (opacity);
|
||||
}
|
||||
|
||||
void Graphics::setFillType (const FillType& newFill)
|
||||
{
|
||||
saveStateIfPending();
|
||||
context.setFill (newFill);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Graphics::setFont (const Font& newFont)
|
||||
{
|
||||
saveStateIfPending();
|
||||
context.setFont (newFont);
|
||||
}
|
||||
|
||||
void Graphics::setFont (const float newFontHeight)
|
||||
{
|
||||
setFont (context.getFont().withHeight (newFontHeight));
|
||||
}
|
||||
|
||||
Font Graphics::getCurrentFont() const
|
||||
{
|
||||
return context.getFont();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY,
|
||||
Justification justification) const
|
||||
{
|
||||
if (text.isEmpty())
|
||||
return;
|
||||
|
||||
// Don't pass any vertical placement flags to this method - they'll be ignored.
|
||||
jassert (justification.getOnlyVerticalFlags() == 0);
|
||||
|
||||
auto flags = justification.getOnlyHorizontalFlags();
|
||||
|
||||
if (flags == Justification::right && startX < context.getClipBounds().getX())
|
||||
return;
|
||||
|
||||
if (flags == Justification::left && startX > context.getClipBounds().getRight())
|
||||
return;
|
||||
|
||||
struct ArrangementArgs
|
||||
{
|
||||
auto tie() const noexcept { return std::tie (font, text); }
|
||||
bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); }
|
||||
|
||||
const Font font;
|
||||
const String text;
|
||||
};
|
||||
|
||||
auto configureArrangement = [] (const ArrangementArgs& args)
|
||||
{
|
||||
GlyphArrangement arrangement;
|
||||
arrangement.addLineOfText (args.font, args.text, 0.0f, 0.0f);
|
||||
return arrangement;
|
||||
};
|
||||
|
||||
using Cache = GlyphArrangementCache<ArrangementArgs>;
|
||||
ArrangementArgs args { context.getFont(), text };
|
||||
const auto arrangement = Cache::getInstance()->get (std::move (args), std::move (configureArrangement));
|
||||
|
||||
const auto transform = std::invoke ([&]
|
||||
{
|
||||
const auto t = AffineTransform::translation ((float) startX,
|
||||
(float) baselineY);
|
||||
|
||||
if (flags == Justification::left)
|
||||
return t;
|
||||
|
||||
auto w = arrangement.getBoundingBox (0, -1, true).getWidth();
|
||||
|
||||
if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0)
|
||||
w /= 2.0f;
|
||||
|
||||
return t.followedBy (AffineTransform::translation (-w, 0));
|
||||
});
|
||||
|
||||
arrangement.draw (*this, transform);
|
||||
}
|
||||
|
||||
void Graphics::drawMultiLineText (const String& text, const int startX,
|
||||
const int baselineY, const int maximumLineWidth,
|
||||
Justification justification, const float leading) const
|
||||
{
|
||||
if (text.isEmpty() || startX >= context.getClipBounds().getRight())
|
||||
return;
|
||||
|
||||
struct ArrangementArgs
|
||||
{
|
||||
auto tie() const noexcept { return std::tie (font, text, maximumLineWidth, justification, leading); }
|
||||
bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); }
|
||||
|
||||
const Font font;
|
||||
const String text;
|
||||
const int maximumLineWidth;
|
||||
const Justification justification;
|
||||
const float leading;
|
||||
};
|
||||
|
||||
auto configureArrangement = [] (const ArrangementArgs& args)
|
||||
{
|
||||
GlyphArrangement arrangement;
|
||||
arrangement.addJustifiedText (args.font, args.text,
|
||||
0.0f, 0.0f, (float) args.maximumLineWidth,
|
||||
args.justification, args.leading);
|
||||
return arrangement;
|
||||
};
|
||||
|
||||
ArrangementArgs args { context.getFont(),
|
||||
text,
|
||||
maximumLineWidth,
|
||||
justification,
|
||||
leading };
|
||||
|
||||
using Cache = GlyphArrangementCache<ArrangementArgs>;
|
||||
Cache::getInstance()->get (std::move (args), std::move (configureArrangement))
|
||||
.draw (*this, AffineTransform::translation ((float) startX,
|
||||
(float) baselineY));
|
||||
}
|
||||
|
||||
void Graphics::drawText (const String& text, Rectangle<float> area,
|
||||
Justification justificationType, bool useEllipsesIfTooBig) const
|
||||
{
|
||||
if (text.isEmpty() || ! context.clipRegionIntersects (area.getSmallestIntegerContainer()))
|
||||
return;
|
||||
|
||||
struct ArrangementArgs
|
||||
{
|
||||
auto tie() const noexcept { return std::tie (font, text, width, height, justificationType, useEllipsesIfTooBig); }
|
||||
bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); }
|
||||
|
||||
const Font font;
|
||||
const String text;
|
||||
const float width;
|
||||
const float height;
|
||||
const Justification justificationType;
|
||||
const bool useEllipsesIfTooBig;
|
||||
};
|
||||
|
||||
auto configureArrangement = [] (const ArrangementArgs& args)
|
||||
{
|
||||
GlyphArrangement arrangement;
|
||||
arrangement.addCurtailedLineOfText (args.font, args.text, 0.0f, 0.0f,
|
||||
args.width, args.useEllipsesIfTooBig);
|
||||
|
||||
arrangement.justifyGlyphs (0, arrangement.getNumGlyphs(),
|
||||
0.0f, 0.0f,
|
||||
args.width, args.height,
|
||||
args.justificationType);
|
||||
return arrangement;
|
||||
};
|
||||
|
||||
ArrangementArgs args { context.getFont(),
|
||||
text,
|
||||
area.getWidth(),
|
||||
area.getHeight(),
|
||||
justificationType,
|
||||
useEllipsesIfTooBig };
|
||||
|
||||
using Cache = GlyphArrangementCache<ArrangementArgs>;
|
||||
Cache::getInstance()->get (std::move (args), std::move (configureArrangement))
|
||||
.draw (*this, AffineTransform::translation (area.getX(), area.getY()));
|
||||
}
|
||||
|
||||
void Graphics::drawText (const String& text, Rectangle<int> area,
|
||||
Justification justificationType, bool useEllipsesIfTooBig) const
|
||||
{
|
||||
drawText (text, area.toFloat(), justificationType, useEllipsesIfTooBig);
|
||||
}
|
||||
|
||||
void Graphics::drawText (const String& text, int x, int y, int width, int height,
|
||||
Justification justificationType, const bool useEllipsesIfTooBig) const
|
||||
{
|
||||
drawText (text, coordsToRectangle (x, y, width, height), justificationType, useEllipsesIfTooBig);
|
||||
}
|
||||
|
||||
void Graphics::drawFittedText (const String& text, Rectangle<int> area,
|
||||
Justification justification,
|
||||
const int maximumNumberOfLines,
|
||||
const float minimumHorizontalScale) const
|
||||
{
|
||||
if (text.isEmpty() || area.isEmpty() || ! context.clipRegionIntersects (area))
|
||||
return;
|
||||
|
||||
struct ArrangementArgs
|
||||
{
|
||||
auto tie() const noexcept { return std::tie (font, text, width, height, justification, maximumNumberOfLines, minimumHorizontalScale); }
|
||||
bool operator< (const ArrangementArgs& other) const noexcept { return tie() < other.tie(); }
|
||||
|
||||
const Font font;
|
||||
const String text;
|
||||
const float width;
|
||||
const float height;
|
||||
const Justification justification;
|
||||
const int maximumNumberOfLines;
|
||||
const float minimumHorizontalScale;
|
||||
};
|
||||
|
||||
auto configureArrangement = [] (const ArrangementArgs& args)
|
||||
{
|
||||
GlyphArrangement arrangement;
|
||||
arrangement.addFittedText (args.font, args.text,
|
||||
0.0f, 0.0f,
|
||||
args.width, args.height,
|
||||
args.justification,
|
||||
args.maximumNumberOfLines,
|
||||
args.minimumHorizontalScale);
|
||||
return arrangement;
|
||||
};
|
||||
|
||||
ArrangementArgs args { context.getFont(),
|
||||
text,
|
||||
(float) area.getWidth(),
|
||||
(float) area.getHeight(),
|
||||
justification,
|
||||
maximumNumberOfLines,
|
||||
minimumHorizontalScale };
|
||||
|
||||
using Cache = GlyphArrangementCache<ArrangementArgs>;
|
||||
Cache::getInstance()->get (std::move (args), std::move (configureArrangement))
|
||||
.draw (*this, AffineTransform::translation ((float) area.getX(),
|
||||
(float) area.getY()));
|
||||
}
|
||||
|
||||
void Graphics::drawFittedText (const String& text, int x, int y, int width, int height,
|
||||
Justification justification,
|
||||
const int maximumNumberOfLines,
|
||||
const float minimumHorizontalScale) const
|
||||
{
|
||||
drawFittedText (text, coordsToRectangle (x, y, width, height),
|
||||
justification, maximumNumberOfLines, minimumHorizontalScale);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Graphics::fillRect (Rectangle<int> r) const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), r)
|
||||
|
||||
context.fillRect (r, false);
|
||||
}
|
||||
|
||||
void Graphics::fillRect (Rectangle<float> r) const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), r)
|
||||
|
||||
context.fillRect (r);
|
||||
}
|
||||
|
||||
void Graphics::fillRect (int x, int y, int width, int height) const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), (Rectangle { x, y, width, height }))
|
||||
|
||||
context.fillRect (coordsToRectangle (x, y, width, height), false);
|
||||
}
|
||||
|
||||
void Graphics::fillRect (float x, float y, float width, float height) const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::fillRect, etw::graphicsKeyword, context.getFrameId(), (Rectangle { x, y, width, height }))
|
||||
|
||||
fillRect (coordsToRectangle (x, y, width, height));
|
||||
}
|
||||
|
||||
void Graphics::fillRectList (const RectangleList<float>& rectangles) const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::fillRectList, etw::graphicsKeyword, context.getFrameId(), rectangles)
|
||||
|
||||
context.fillRectList (rectangles);
|
||||
}
|
||||
|
||||
void Graphics::fillRectList (const RectangleList<int>& rects) const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_I32 (etw::fillRectList, etw::graphicsKeyword, context.getFrameId(), rects)
|
||||
|
||||
RectangleList<float> converted;
|
||||
|
||||
for (const auto& r : rects)
|
||||
converted.add (r.toFloat());
|
||||
|
||||
context.fillRectList (converted);
|
||||
}
|
||||
|
||||
void Graphics::fillAll() const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillAll, etw::graphicsKeyword, context.getFrameId())
|
||||
|
||||
context.fillAll();
|
||||
}
|
||||
|
||||
void Graphics::fillAll (Colour colourToUse) const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillAll, etw::graphicsKeyword, context.getFrameId())
|
||||
|
||||
if (! colourToUse.isTransparent())
|
||||
{
|
||||
context.saveState();
|
||||
context.setFill (colourToUse);
|
||||
context.fillAll();
|
||||
context.restoreState();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Graphics::fillPath (const Path& path) const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillPath, etw::graphicsKeyword, context.getFrameId());
|
||||
|
||||
if (! (context.isClipEmpty() || path.isEmpty()))
|
||||
context.fillPath (path, AffineTransform());
|
||||
}
|
||||
|
||||
void Graphics::fillPath (const Path& path, const AffineTransform& transform) const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::fillPath, etw::graphicsKeyword, context.getFrameId())
|
||||
|
||||
if (! (context.isClipEmpty() || path.isEmpty()))
|
||||
context.fillPath (path, transform);
|
||||
}
|
||||
|
||||
void Graphics::strokePath (const Path& path,
|
||||
const PathStrokeType& strokeType,
|
||||
const AffineTransform& transform) const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME (etw::strokePath, etw::graphicsKeyword, context.getFrameId())
|
||||
|
||||
if (! (context.isClipEmpty() || path.isEmpty()))
|
||||
context.strokePath (path, strokeType, transform);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Graphics::drawRect (float x, float y, float width, float height, float lineThickness) const
|
||||
{
|
||||
drawRect (coordsToRectangle (x, y, width, height), lineThickness);
|
||||
}
|
||||
|
||||
void Graphics::drawRect (int x, int y, int width, int height, int lineThickness) const
|
||||
{
|
||||
drawRect (coordsToRectangle (x, y, width, height), lineThickness);
|
||||
}
|
||||
|
||||
void Graphics::drawRect (Rectangle<int> r, int lineThickness) const
|
||||
{
|
||||
drawRect (r.toFloat(), (float) lineThickness);
|
||||
}
|
||||
|
||||
void Graphics::drawRect (Rectangle<float> r, const float lineThickness) const
|
||||
{
|
||||
JUCE_SCOPED_TRACE_EVENT_FRAME_RECT_F32 (etw::drawRect, etw::graphicsKeyword, context.getFrameId(), r)
|
||||
|
||||
jassert (r.getWidth() >= 0.0f && r.getHeight() >= 0.0f);
|
||||
context.drawRect (r, lineThickness);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Graphics::fillEllipse (Rectangle<float> area) const
|
||||
{
|
||||
context.fillEllipse (area);
|
||||
}
|
||||
|
||||
void Graphics::fillEllipse (float x, float y, float w, float h) const
|
||||
{
|
||||
fillEllipse (coordsToRectangle (x, y, w, h));
|
||||
}
|
||||
|
||||
void Graphics::drawEllipse (float x, float y, float width, float height, float lineThickness) const
|
||||
{
|
||||
drawEllipse (coordsToRectangle (x, y, width, height), lineThickness);
|
||||
}
|
||||
|
||||
void Graphics::drawEllipse (Rectangle<float> area, float lineThickness) const
|
||||
{
|
||||
context.drawEllipse (area, lineThickness);
|
||||
}
|
||||
|
||||
void Graphics::fillRoundedRectangle (float x, float y, float width, float height, float cornerSize) const
|
||||
{
|
||||
fillRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize);
|
||||
}
|
||||
|
||||
void Graphics::fillRoundedRectangle (Rectangle<float> r, const float cornerSize) const
|
||||
{
|
||||
context.fillRoundedRectangle (r, cornerSize);
|
||||
}
|
||||
|
||||
void Graphics::drawRoundedRectangle (float x, float y, float width, float height,
|
||||
float cornerSize, float lineThickness) const
|
||||
{
|
||||
drawRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize, lineThickness);
|
||||
}
|
||||
|
||||
void Graphics::drawRoundedRectangle (Rectangle<float> r, float cornerSize, float lineThickness) const
|
||||
{
|
||||
context.drawRoundedRectangle (r, cornerSize, lineThickness);
|
||||
}
|
||||
|
||||
void Graphics::drawArrow (Line<float> line, float lineThickness, float arrowheadWidth, float arrowheadLength) const
|
||||
{
|
||||
Path p;
|
||||
p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength);
|
||||
fillPath (p);
|
||||
}
|
||||
|
||||
void Graphics::fillCheckerBoard (Rectangle<float> area, float checkWidth, float checkHeight,
|
||||
Colour colour1, Colour colour2) const
|
||||
{
|
||||
jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less!
|
||||
|
||||
if (checkWidth > 0 && checkHeight > 0)
|
||||
{
|
||||
context.saveState();
|
||||
|
||||
if (colour1 == colour2)
|
||||
{
|
||||
context.setFill (colour1);
|
||||
context.fillRect (area);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto clipped = context.getClipBounds().getIntersection (area.getSmallestIntegerContainer());
|
||||
|
||||
if (! clipped.isEmpty())
|
||||
{
|
||||
const int checkNumX = (int) (((float) clipped.getX() - area.getX()) / checkWidth);
|
||||
const int checkNumY = (int) (((float) clipped.getY() - area.getY()) / checkHeight);
|
||||
const float startX = area.getX() + (float) checkNumX * checkWidth;
|
||||
const float startY = area.getY() + (float) checkNumY * checkHeight;
|
||||
const float right = (float) clipped.getRight();
|
||||
const float bottom = (float) clipped.getBottom();
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
int cy = i;
|
||||
RectangleList<float> checks;
|
||||
|
||||
for (float y = startY; y < bottom; y += checkHeight)
|
||||
for (float x = startX + (cy++ & 1) * checkWidth; x < right; x += checkWidth * 2.0f)
|
||||
checks.addWithoutMerging ({ x, y, checkWidth, checkHeight });
|
||||
|
||||
checks.clipTo (area);
|
||||
context.setFill (i == ((checkNumX ^ checkNumY) & 1) ? colour1 : colour2);
|
||||
context.fillRectList (checks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.restoreState();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Graphics::drawVerticalLine (const int x, float top, float bottom) const
|
||||
{
|
||||
if (top < bottom)
|
||||
context.fillRect (Rectangle<float> ((float) x, top, 1.0f, bottom - top));
|
||||
}
|
||||
|
||||
void Graphics::drawHorizontalLine (const int y, float left, float right) const
|
||||
{
|
||||
if (left < right)
|
||||
context.fillRect (Rectangle<float> (left, (float) y, right - left, 1.0f));
|
||||
}
|
||||
|
||||
void Graphics::drawLine (Line<float> line) const
|
||||
{
|
||||
context.drawLine (line);
|
||||
}
|
||||
|
||||
void Graphics::drawLine (float x1, float y1, float x2, float y2) const
|
||||
{
|
||||
context.drawLine (Line<float> (x1, y1, x2, y2));
|
||||
}
|
||||
|
||||
void Graphics::drawLine (float x1, float y1, float x2, float y2, float lineThickness) const
|
||||
{
|
||||
drawLine (Line<float> (x1, y1, x2, y2), lineThickness);
|
||||
}
|
||||
|
||||
void Graphics::drawLine (Line<float> line, const float lineThickness) const
|
||||
{
|
||||
context.drawLineWithThickness (line, lineThickness);
|
||||
}
|
||||
|
||||
void Graphics::drawDashedLine (Line<float> line, const float* dashLengths,
|
||||
int numDashLengths, float lineThickness, int n) const
|
||||
{
|
||||
jassert (n >= 0 && n < numDashLengths); // your start index must be valid!
|
||||
|
||||
const Point<double> delta ((line.getEnd() - line.getStart()).toDouble());
|
||||
const double totalLen = delta.getDistanceFromOrigin();
|
||||
|
||||
if (totalLen >= 0.1)
|
||||
{
|
||||
const double onePixAlpha = 1.0 / totalLen;
|
||||
|
||||
for (double alpha = 0.0; alpha < 1.0;)
|
||||
{
|
||||
jassert (dashLengths[n] > 0); // can't have zero-length dashes!
|
||||
|
||||
const double lastAlpha = alpha;
|
||||
alpha += dashLengths [n] * onePixAlpha;
|
||||
n = (n + 1) % numDashLengths;
|
||||
|
||||
if ((n & 1) != 0)
|
||||
{
|
||||
const Line<float> segment (line.getStart() + (delta * lastAlpha).toFloat(),
|
||||
line.getStart() + (delta * jmin (1.0, alpha)).toFloat());
|
||||
|
||||
if (! approximatelyEqual (lineThickness, 1.0f))
|
||||
drawLine (segment, lineThickness);
|
||||
else
|
||||
context.drawLine (segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality)
|
||||
{
|
||||
saveStateIfPending();
|
||||
context.setInterpolationQuality (newQuality);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Graphics::drawImageAt (const Image& imageToDraw, int x, int y, bool fillAlphaChannel) const
|
||||
{
|
||||
drawImageTransformed (imageToDraw,
|
||||
AffineTransform::translation ((float) x, (float) y),
|
||||
fillAlphaChannel);
|
||||
}
|
||||
|
||||
void Graphics::drawImage (const Image& imageToDraw, Rectangle<float> targetArea,
|
||||
RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const
|
||||
{
|
||||
if (imageToDraw.isValid())
|
||||
drawImageTransformed (imageToDraw,
|
||||
placementWithinTarget.getTransformToFit (imageToDraw.getBounds().toFloat(), targetArea),
|
||||
fillAlphaChannelWithCurrentBrush);
|
||||
}
|
||||
|
||||
void Graphics::drawImageWithin (const Image& imageToDraw, int dx, int dy, int dw, int dh,
|
||||
RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const
|
||||
{
|
||||
drawImage (imageToDraw, coordsToRectangle (dx, dy, dw, dh).toFloat(),
|
||||
placementWithinTarget, fillAlphaChannelWithCurrentBrush);
|
||||
}
|
||||
|
||||
void Graphics::drawImage (const Image& imageToDraw,
|
||||
int dx, int dy, int dw, int dh,
|
||||
int sx, int sy, int sw, int sh,
|
||||
const bool fillAlphaChannelWithCurrentBrush) const
|
||||
{
|
||||
if (imageToDraw.isValid() && context.clipRegionIntersects (coordsToRectangle (dx, dy, dw, dh)))
|
||||
drawImageTransformed (imageToDraw.getClippedImage (coordsToRectangle (sx, sy, sw, sh)),
|
||||
AffineTransform::scale ((float) dw / (float) sw, (float) dh / (float) sh)
|
||||
.translated ((float) dx, (float) dy),
|
||||
fillAlphaChannelWithCurrentBrush);
|
||||
}
|
||||
|
||||
void Graphics::drawImageTransformed (const Image& imageToDraw,
|
||||
const AffineTransform& transform,
|
||||
const bool fillAlphaChannelWithCurrentBrush) const
|
||||
{
|
||||
if (imageToDraw.isValid() && ! context.isClipEmpty())
|
||||
{
|
||||
if (fillAlphaChannelWithCurrentBrush)
|
||||
{
|
||||
context.saveState();
|
||||
context.clipToImageAlpha (imageToDraw, transform);
|
||||
fillAll();
|
||||
context.restoreState();
|
||||
}
|
||||
else
|
||||
{
|
||||
context.drawImage (imageToDraw, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Graphics::ScopedSaveState::ScopedSaveState (Graphics& g) : context (g)
|
||||
{
|
||||
context.saveState();
|
||||
}
|
||||
|
||||
Graphics::ScopedSaveState::~ScopedSaveState()
|
||||
{
|
||||
context.restoreState();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
class GraphicsTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
GraphicsTests() : UnitTest ("Graphics", UnitTestCategories::graphics) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("Render image subsection");
|
||||
{
|
||||
const SoftwareImageType softwareImageType;
|
||||
const NativeImageType nativeImageType;
|
||||
const ImageType* types[] { &softwareImageType, &nativeImageType };
|
||||
|
||||
for (auto* sourceType : types)
|
||||
for (auto* targetType : types)
|
||||
renderImageSubsection (*sourceType, *targetType);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void renderImageSubsection (const ImageType& sourceType, const ImageType& targetType)
|
||||
{
|
||||
const auto sourceColour = Colours::cyan;
|
||||
const auto sourceOffset = 49;
|
||||
|
||||
const Image source { Image::ARGB, 50, 50, true, sourceType };
|
||||
const Image target { Image::ARGB, 50, 50, true, targetType };
|
||||
|
||||
const auto subsection = source.getClippedImage (Rectangle { sourceOffset, sourceOffset, 1, 1 });
|
||||
|
||||
Image::BitmapData { subsection, Image::BitmapData::writeOnly }.setPixelColour (0, 0, sourceColour);
|
||||
|
||||
{
|
||||
// Render the subsection image so that it fills 'target'
|
||||
Graphics g { target };
|
||||
// Use low resampling quality, because we want to avoid our pixel getting blurry when it's scaled up
|
||||
g.setImageResamplingQuality (Graphics::lowResamplingQuality);
|
||||
g.drawImage (subsection,
|
||||
0, 0, target.getWidth(), target.getHeight(),
|
||||
0, 0, 1, 1);
|
||||
}
|
||||
|
||||
{
|
||||
// Check that all pixels in 'target' match the bottom right pixel of 'source'
|
||||
const Image::BitmapData bitmap { target, Image::BitmapData::readOnly };
|
||||
|
||||
int numFailures = 0;
|
||||
|
||||
for (auto y = 0; y < bitmap.height; ++y)
|
||||
{
|
||||
for (auto x = 0; x < bitmap.width; ++x)
|
||||
{
|
||||
const auto targetColour = bitmap.getPixelColour (x, y);
|
||||
|
||||
if (targetColour != sourceColour)
|
||||
++numFailures;
|
||||
}
|
||||
}
|
||||
|
||||
expect (numFailures == 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static GraphicsTests graphicsTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A graphics context, used for drawing a component or image.
|
||||
|
||||
When a Component needs painting, a Graphics context is passed to its
|
||||
Component::paint() method, and this you then call methods within this
|
||||
object to actually draw the component's content.
|
||||
|
||||
A Graphics can also be created from an image, to allow drawing directly onto
|
||||
that image.
|
||||
|
||||
@see Component::paint
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API Graphics final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a Graphics object to draw directly onto the given image.
|
||||
|
||||
The graphics object that is created will be set up to draw onto the image,
|
||||
with the context's clipping area being the entire size of the image, and its
|
||||
origin being the image's origin. To draw into a subsection of an image, use the
|
||||
reduceClipRegion() and setOrigin() methods.
|
||||
|
||||
Obviously you shouldn't delete the image before this context is deleted.
|
||||
*/
|
||||
explicit Graphics (const Image& imageToDrawOnto);
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the current drawing colour.
|
||||
|
||||
This sets the colour that will now be used for drawing operations - it also
|
||||
sets the opacity to that of the colour passed-in.
|
||||
|
||||
If a brush is being used when this method is called, the brush will be deselected,
|
||||
and any subsequent drawing will be done with a solid colour brush instead.
|
||||
|
||||
@see setOpacity
|
||||
*/
|
||||
void setColour (Colour newColour);
|
||||
|
||||
/** Changes the opacity to use with the current colour.
|
||||
|
||||
If a solid colour is being used for drawing, this changes its opacity
|
||||
to this new value (i.e. it doesn't multiply the colour's opacity by this amount).
|
||||
|
||||
If a gradient is being used, this will have no effect on it.
|
||||
|
||||
A value of 0.0 is completely transparent, 1.0 is completely opaque.
|
||||
*/
|
||||
void setOpacity (float newOpacity);
|
||||
|
||||
/** Sets the context to use a gradient for its fill pattern. */
|
||||
void setGradientFill (const ColourGradient& gradient);
|
||||
|
||||
/** Sets the context to use a gradient for its fill pattern. */
|
||||
void setGradientFill (ColourGradient&& gradient);
|
||||
|
||||
/** Sets the context to use a tiled image pattern for filling.
|
||||
Make sure that you don't delete this image while it's still being used by
|
||||
this context!
|
||||
*/
|
||||
void setTiledImageFill (const Image& imageToUse,
|
||||
int anchorX, int anchorY,
|
||||
float opacity);
|
||||
|
||||
/** Changes the current fill settings.
|
||||
@see setColour, setGradientFill, setTiledImageFill
|
||||
*/
|
||||
void setFillType (const FillType& newFill);
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the font to use for subsequent text-drawing functions.
|
||||
@see drawSingleLineText, drawMultiLineText, drawText, drawFittedText
|
||||
*/
|
||||
void setFont (const Font& newFont);
|
||||
|
||||
/** Changes the size of the currently-selected font.
|
||||
This is a convenient shortcut that changes the context's current font to a
|
||||
different size. The typeface won't be changed.
|
||||
@see Font
|
||||
*/
|
||||
void setFont (float newFontHeight);
|
||||
|
||||
/** Returns the currently selected font. */
|
||||
Font getCurrentFont() const;
|
||||
|
||||
/** Draws a one-line text string.
|
||||
|
||||
This will use the current colour (or brush) to fill the text. The font is the last
|
||||
one specified by setFont().
|
||||
|
||||
@param text the string to draw
|
||||
@param startX the position to draw the left-hand edge of the text
|
||||
@param baselineY the position of the text's baseline
|
||||
@param justification the horizontal flags indicate which end of the text string is
|
||||
anchored at the specified point.
|
||||
@see drawMultiLineText, drawText, drawFittedText, GlyphArrangement::addLineOfText
|
||||
*/
|
||||
void drawSingleLineText (const String& text,
|
||||
int startX, int baselineY,
|
||||
Justification justification = Justification::left) const;
|
||||
|
||||
/** Draws text across multiple lines.
|
||||
|
||||
This will break the text onto a new line where there's a new-line or
|
||||
carriage-return character, or at a word-boundary when the text becomes wider
|
||||
than the size specified by the maximumLineWidth parameter. New-lines
|
||||
will be vertically separated by the specified leading.
|
||||
|
||||
@see setFont, drawSingleLineText, drawFittedText, GlyphArrangement::addJustifiedText
|
||||
*/
|
||||
void drawMultiLineText (const String& text,
|
||||
int startX, int baselineY,
|
||||
int maximumLineWidth,
|
||||
Justification justification = Justification::left,
|
||||
float leading = 0.0f) const;
|
||||
|
||||
/** Draws a line of text within a specified rectangle.
|
||||
|
||||
The text will be positioned within the rectangle based on the justification
|
||||
flags passed-in. If the string is too long to fit inside the rectangle, it will
|
||||
either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig
|
||||
flag is true).
|
||||
|
||||
@see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText
|
||||
*/
|
||||
void drawText (const String& text,
|
||||
int x, int y, int width, int height,
|
||||
Justification justificationType,
|
||||
bool useEllipsesIfTooBig = true) const;
|
||||
|
||||
/** Draws a line of text within a specified rectangle.
|
||||
|
||||
The text will be positioned within the rectangle based on the justification
|
||||
flags passed-in. If the string is too long to fit inside the rectangle, it will
|
||||
either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig
|
||||
flag is true).
|
||||
|
||||
@see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText
|
||||
*/
|
||||
void drawText (const String& text,
|
||||
Rectangle<int> area,
|
||||
Justification justificationType,
|
||||
bool useEllipsesIfTooBig = true) const;
|
||||
|
||||
/** Draws a line of text within a specified rectangle.
|
||||
|
||||
The text will be positioned within the rectangle based on the justification
|
||||
flags passed-in. If the string is too long to fit inside the rectangle, it will
|
||||
either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig
|
||||
flag is true).
|
||||
|
||||
@see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText
|
||||
*/
|
||||
void drawText (const String& text,
|
||||
Rectangle<float> area,
|
||||
Justification justificationType,
|
||||
bool useEllipsesIfTooBig = true) const;
|
||||
|
||||
/** Tries to draw a text string inside a given space.
|
||||
|
||||
This does its best to make the given text readable within the specified rectangle,
|
||||
so it's useful for labelling things.
|
||||
|
||||
If the text is too big, it'll be squashed horizontally or broken over multiple lines
|
||||
if the maximumLinesToUse value allows this. If the text just won't fit into the space,
|
||||
it'll cram as much as possible in there, and put some ellipsis at the end to show that
|
||||
it's been truncated.
|
||||
|
||||
A Justification parameter lets you specify how the text is laid out within the rectangle,
|
||||
both horizontally and vertically.
|
||||
|
||||
The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally
|
||||
to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you
|
||||
can set this value to 1.0f. Pass 0 if you want it to use a default value.
|
||||
|
||||
@see GlyphArrangement::addFittedText
|
||||
*/
|
||||
void drawFittedText (const String& text,
|
||||
int x, int y, int width, int height,
|
||||
Justification justificationFlags,
|
||||
int maximumNumberOfLines,
|
||||
float minimumHorizontalScale = 0.0f) const;
|
||||
|
||||
/** Tries to draw a text string inside a given space.
|
||||
|
||||
This does its best to make the given text readable within the specified rectangle,
|
||||
so it's useful for labelling things.
|
||||
|
||||
If the text is too big, it'll be squashed horizontally or broken over multiple lines
|
||||
if the maximumLinesToUse value allows this. If the text just won't fit into the space,
|
||||
it'll cram as much as possible in there, and put some ellipsis at the end to show that
|
||||
it's been truncated.
|
||||
|
||||
A Justification parameter lets you specify how the text is laid out within the rectangle,
|
||||
both horizontally and vertically.
|
||||
|
||||
The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally
|
||||
to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you
|
||||
can set this value to 1.0f. Pass 0 if you want it to use a default value.
|
||||
|
||||
@see GlyphArrangement::addFittedText
|
||||
*/
|
||||
void drawFittedText (const String& text,
|
||||
Rectangle<int> area,
|
||||
Justification justificationFlags,
|
||||
int maximumNumberOfLines,
|
||||
float minimumHorizontalScale = 0.0f) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Fills the context's entire clip region with the current colour or brush.
|
||||
|
||||
(See also the fillAll (Colour) method which is a quick way of filling
|
||||
it with a given colour).
|
||||
*/
|
||||
void fillAll() const;
|
||||
|
||||
/** Fills the context's entire clip region with a given colour.
|
||||
|
||||
This leaves the context's current colour and brush unchanged, it just
|
||||
uses the specified colour temporarily.
|
||||
*/
|
||||
void fillAll (Colour colourToUse) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Fills a rectangle with the current colour or brush.
|
||||
@see drawRect, fillRoundedRectangle
|
||||
*/
|
||||
void fillRect (Rectangle<int> rectangle) const;
|
||||
|
||||
/** Fills a rectangle with the current colour or brush.
|
||||
@see drawRect, fillRoundedRectangle
|
||||
*/
|
||||
void fillRect (Rectangle<float> rectangle) const;
|
||||
|
||||
/** Fills a rectangle with the current colour or brush.
|
||||
@see drawRect, fillRoundedRectangle
|
||||
*/
|
||||
void fillRect (int x, int y, int width, int height) const;
|
||||
|
||||
/** Fills a rectangle with the current colour or brush.
|
||||
@see drawRect, fillRoundedRectangle
|
||||
*/
|
||||
void fillRect (float x, float y, float width, float height) const;
|
||||
|
||||
/** Fills a set of rectangles using the current colour or brush.
|
||||
If you have a lot of rectangles to draw, it may be more efficient
|
||||
to create a RectangleList and use this method than to call fillRect()
|
||||
multiple times.
|
||||
*/
|
||||
void fillRectList (const RectangleList<float>& rectangles) const;
|
||||
|
||||
/** Fills a set of rectangles using the current colour or brush.
|
||||
If you have a lot of rectangles to draw, it may be more efficient
|
||||
to create a RectangleList and use this method than to call fillRect()
|
||||
multiple times.
|
||||
*/
|
||||
void fillRectList (const RectangleList<int>& rectangles) const;
|
||||
|
||||
/** Uses the current colour or brush to fill a rectangle with rounded corners.
|
||||
@see drawRoundedRectangle, Path::addRoundedRectangle
|
||||
*/
|
||||
void fillRoundedRectangle (float x, float y, float width, float height,
|
||||
float cornerSize) const;
|
||||
|
||||
/** Uses the current colour or brush to fill a rectangle with rounded corners.
|
||||
@see drawRoundedRectangle, Path::addRoundedRectangle
|
||||
*/
|
||||
void fillRoundedRectangle (Rectangle<float> rectangle,
|
||||
float cornerSize) const;
|
||||
|
||||
/** Fills a rectangle with a checkerboard pattern, alternating between two colours. */
|
||||
void fillCheckerBoard (Rectangle<float> area,
|
||||
float checkWidth, float checkHeight,
|
||||
Colour colour1, Colour colour2) const;
|
||||
|
||||
/** Draws a rectangular outline, using the current colour or brush.
|
||||
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards.
|
||||
@see fillRect
|
||||
*/
|
||||
void drawRect (int x, int y, int width, int height, int lineThickness = 1) const;
|
||||
|
||||
/** Draws a rectangular outline, using the current colour or brush.
|
||||
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards.
|
||||
@see fillRect
|
||||
*/
|
||||
void drawRect (float x, float y, float width, float height, float lineThickness = 1.0f) const;
|
||||
|
||||
/** Draws a rectangular outline, using the current colour or brush.
|
||||
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards.
|
||||
@see fillRect
|
||||
*/
|
||||
void drawRect (Rectangle<int> rectangle, int lineThickness = 1) const;
|
||||
|
||||
/** Draws a rectangular outline, using the current colour or brush.
|
||||
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards.
|
||||
@see fillRect
|
||||
*/
|
||||
void drawRect (Rectangle<float> rectangle, float lineThickness = 1.0f) const;
|
||||
|
||||
/** Uses the current colour or brush to draw the outline of a rectangle with rounded corners.
|
||||
@see fillRoundedRectangle, Path::addRoundedRectangle
|
||||
*/
|
||||
void drawRoundedRectangle (float x, float y, float width, float height,
|
||||
float cornerSize, float lineThickness) const;
|
||||
|
||||
/** Uses the current colour or brush to draw the outline of a rectangle with rounded corners.
|
||||
@see fillRoundedRectangle, Path::addRoundedRectangle
|
||||
*/
|
||||
void drawRoundedRectangle (Rectangle<float> rectangle,
|
||||
float cornerSize, float lineThickness) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Fills an ellipse with the current colour or brush.
|
||||
The ellipse is drawn to fit inside the given rectangle.
|
||||
@see drawEllipse, Path::addEllipse
|
||||
*/
|
||||
void fillEllipse (float x, float y, float width, float height) const;
|
||||
|
||||
/** Fills an ellipse with the current colour or brush.
|
||||
The ellipse is drawn to fit inside the given rectangle.
|
||||
@see drawEllipse, Path::addEllipse
|
||||
*/
|
||||
void fillEllipse (Rectangle<float> area) const;
|
||||
|
||||
/** Draws an elliptical stroke using the current colour or brush.
|
||||
@see fillEllipse, Path::addEllipse
|
||||
*/
|
||||
void drawEllipse (float x, float y, float width, float height,
|
||||
float lineThickness) const;
|
||||
|
||||
/** Draws an elliptical stroke using the current colour or brush.
|
||||
@see fillEllipse, Path::addEllipse
|
||||
*/
|
||||
void drawEllipse (Rectangle<float> area, float lineThickness) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Draws a line between two points.
|
||||
The line is 1 pixel wide and drawn with the current colour or brush.
|
||||
TIP: If you're trying to draw horizontal or vertical lines, don't use this -
|
||||
it's better to use fillRect() instead unless you really need an angled line.
|
||||
*/
|
||||
void drawLine (float startX, float startY, float endX, float endY) const;
|
||||
|
||||
/** Draws a line between two points with a given thickness.
|
||||
TIP: If you're trying to draw horizontal or vertical lines, don't use this -
|
||||
it's better to use fillRect() instead unless you really need an angled line.
|
||||
@see Path::addLineSegment
|
||||
*/
|
||||
void drawLine (float startX, float startY, float endX, float endY, float lineThickness) const;
|
||||
|
||||
/** Draws a line between two points.
|
||||
The line is 1 pixel wide and drawn with the current colour or brush.
|
||||
TIP: If you're trying to draw horizontal or vertical lines, don't use this -
|
||||
it's better to use fillRect() instead unless you really need an angled line.
|
||||
*/
|
||||
void drawLine (Line<float> line) const;
|
||||
|
||||
/** Draws a line between two points with a given thickness.
|
||||
@see Path::addLineSegment
|
||||
TIP: If you're trying to draw horizontal or vertical lines, don't use this -
|
||||
it's better to use fillRect() instead unless you really need an angled line.
|
||||
*/
|
||||
void drawLine (Line<float> line, float lineThickness) const;
|
||||
|
||||
/** Draws a dashed line using a custom set of dash-lengths.
|
||||
|
||||
@param line the line to draw
|
||||
@param dashLengths a series of lengths to specify the on/off lengths - e.g.
|
||||
{ 4, 5, 6, 7 } will draw a line of 4 pixels, skip 5 pixels,
|
||||
draw 6 pixels, skip 7 pixels, and then repeat.
|
||||
@param numDashLengths the number of elements in the array (this must be an even number).
|
||||
@param lineThickness the thickness of the line to draw
|
||||
@param dashIndexToStartFrom the index in the dash-length array to use for the first segment
|
||||
@see PathStrokeType::createDashedStroke
|
||||
*/
|
||||
void drawDashedLine (Line<float> line,
|
||||
const float* dashLengths, int numDashLengths,
|
||||
float lineThickness = 1.0f,
|
||||
int dashIndexToStartFrom = 0) const;
|
||||
|
||||
/** Draws a vertical line of pixels at a given x position.
|
||||
|
||||
The x position is an integer, but the top and bottom of the line can be sub-pixel
|
||||
positions, and these will be anti-aliased if necessary.
|
||||
|
||||
The bottom parameter must be greater than or equal to the top parameter.
|
||||
*/
|
||||
void drawVerticalLine (int x, float top, float bottom) const;
|
||||
|
||||
/** Draws a horizontal line of pixels at a given y position.
|
||||
|
||||
The y position is an integer, but the left and right ends of the line can be sub-pixel
|
||||
positions, and these will be anti-aliased if necessary.
|
||||
|
||||
The right parameter must be greater than or equal to the left parameter.
|
||||
*/
|
||||
void drawHorizontalLine (int y, float left, float right) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Fills a path using the currently selected colour or brush. */
|
||||
void fillPath (const Path& path) const;
|
||||
|
||||
/** Fills a path using the currently selected colour or brush, and adds a transform. */
|
||||
void fillPath (const Path& path, const AffineTransform& transform) const;
|
||||
|
||||
/** Draws a path's outline using the currently selected colour or brush. */
|
||||
void strokePath (const Path& path,
|
||||
const PathStrokeType& strokeType,
|
||||
const AffineTransform& transform = {}) const;
|
||||
|
||||
/** Draws a line with an arrowhead at its end.
|
||||
|
||||
@param line the line to draw
|
||||
@param lineThickness the thickness of the line
|
||||
@param arrowheadWidth the width of the arrow head (perpendicular to the line)
|
||||
@param arrowheadLength the length of the arrow head (along the length of the line)
|
||||
*/
|
||||
void drawArrow (Line<float> line,
|
||||
float lineThickness,
|
||||
float arrowheadWidth,
|
||||
float arrowheadLength) const;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Types of rendering quality that can be specified when drawing images.
|
||||
|
||||
@see Graphics::setImageResamplingQuality
|
||||
*/
|
||||
enum ResamplingQuality
|
||||
{
|
||||
lowResamplingQuality = 0, /**< Just uses a nearest-neighbour algorithm for resampling. */
|
||||
mediumResamplingQuality = 1, /**< Uses bilinear interpolation for upsampling and area-averaging for downsampling. */
|
||||
highResamplingQuality = 2, /**< Uses bicubic interpolation for upsampling and area-averaging for downsampling. */
|
||||
};
|
||||
|
||||
/** Changes the quality that will be used when resampling images.
|
||||
By default a Graphics object will be set to mediumRenderingQuality.
|
||||
@see Graphics::drawImage, Graphics::drawImageTransformed, Graphics::drawImageWithin
|
||||
*/
|
||||
void setImageResamplingQuality (ResamplingQuality newQuality);
|
||||
|
||||
/** Draws an image.
|
||||
|
||||
This will draw the whole of an image, positioning its top-left corner at the
|
||||
given coordinates, and keeping its size the same. This is the simplest image
|
||||
drawing method - the others give more control over the scaling and clipping
|
||||
of the images.
|
||||
|
||||
Images are composited using the context's current opacity, so if you
|
||||
don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f)
|
||||
(or setColour() with an opaque colour) before drawing images.
|
||||
*/
|
||||
void drawImageAt (const Image& imageToDraw, int topLeftX, int topLeftY,
|
||||
bool fillAlphaChannelWithCurrentBrush = false) const;
|
||||
|
||||
/** Draws part of an image, rescaling it to fit in a given target region.
|
||||
|
||||
The specified area of the source image is rescaled and drawn to fill the
|
||||
specified destination rectangle.
|
||||
|
||||
Images are composited using the context's current opacity, so if you
|
||||
don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f)
|
||||
(or setColour() with an opaque colour) before drawing images.
|
||||
|
||||
@param imageToDraw the image to overlay
|
||||
@param destX the left of the destination rectangle
|
||||
@param destY the top of the destination rectangle
|
||||
@param destWidth the width of the destination rectangle
|
||||
@param destHeight the height of the destination rectangle
|
||||
@param sourceX the left of the rectangle to copy from the source image
|
||||
@param sourceY the top of the rectangle to copy from the source image
|
||||
@param sourceWidth the width of the rectangle to copy from the source image
|
||||
@param sourceHeight the height of the rectangle to copy from the source image
|
||||
@param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the source image's pixels,
|
||||
the source image's alpha channel is used as a mask with
|
||||
which to fill the destination using the current colour
|
||||
or brush. (If the source is has no alpha channel, then
|
||||
it will just fill the target with a solid rectangle)
|
||||
@see setImageResamplingQuality, drawImageAt, drawImageWithin, fillAlphaMap
|
||||
*/
|
||||
void drawImage (const Image& imageToDraw,
|
||||
int destX, int destY, int destWidth, int destHeight,
|
||||
int sourceX, int sourceY, int sourceWidth, int sourceHeight,
|
||||
bool fillAlphaChannelWithCurrentBrush = false) const;
|
||||
|
||||
/** Draws an image, having applied an affine transform to it.
|
||||
|
||||
This lets you throw the image around in some wacky ways, rotate it, shear,
|
||||
scale it, etc.
|
||||
|
||||
Images are composited using the context's current opacity, so if you
|
||||
don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f)
|
||||
(or setColour() with an opaque colour) before drawing images.
|
||||
|
||||
If fillAlphaChannelWithCurrentBrush is set to true, then the image's RGB channels
|
||||
are ignored and it is filled with the current brush, masked by its alpha channel.
|
||||
|
||||
If you want to render only a subsection of an image, use Image::getClippedImage() to
|
||||
create the section that you need.
|
||||
|
||||
@see setImageResamplingQuality, drawImage
|
||||
*/
|
||||
void drawImageTransformed (const Image& imageToDraw,
|
||||
const AffineTransform& transform,
|
||||
bool fillAlphaChannelWithCurrentBrush = false) const;
|
||||
|
||||
/** Draws an image to fit within a designated rectangle.
|
||||
|
||||
@param imageToDraw the source image to draw
|
||||
@param targetArea the target rectangle to fit it into
|
||||
@param placementWithinTarget this specifies how the image should be positioned
|
||||
within the target rectangle - see the RectanglePlacement
|
||||
class for more details about this.
|
||||
@param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the image, just its
|
||||
alpha channel will be used as a mask with which to
|
||||
draw with the current brush or colour. This is
|
||||
similar to fillAlphaMap(), and see also drawImage()
|
||||
@see drawImage, drawImageTransformed, drawImageAt, RectanglePlacement
|
||||
*/
|
||||
void drawImage (const Image& imageToDraw, Rectangle<float> targetArea,
|
||||
RectanglePlacement placementWithinTarget = RectanglePlacement::stretchToFit,
|
||||
bool fillAlphaChannelWithCurrentBrush = false) const;
|
||||
|
||||
/** Draws an image to fit within a designated rectangle.
|
||||
|
||||
If the image is too big or too small for the space, it will be rescaled
|
||||
to fit as nicely as it can do without affecting its aspect ratio. It will
|
||||
then be placed within the target rectangle according to the justification flags
|
||||
specified.
|
||||
|
||||
@param imageToDraw the source image to draw
|
||||
@param destX top-left of the target rectangle to fit it into
|
||||
@param destY top-left of the target rectangle to fit it into
|
||||
@param destWidth size of the target rectangle to fit the image into
|
||||
@param destHeight size of the target rectangle to fit the image into
|
||||
@param placementWithinTarget this specifies how the image should be positioned
|
||||
within the target rectangle - see the RectanglePlacement
|
||||
class for more details about this.
|
||||
@param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the image, just its
|
||||
alpha channel will be used as a mask with which to
|
||||
draw with the current brush or colour. This is
|
||||
similar to fillAlphaMap(), and see also drawImage()
|
||||
@see setImageResamplingQuality, drawImage, drawImageTransformed, drawImageAt, RectanglePlacement
|
||||
*/
|
||||
void drawImageWithin (const Image& imageToDraw,
|
||||
int destX, int destY, int destWidth, int destHeight,
|
||||
RectanglePlacement placementWithinTarget,
|
||||
bool fillAlphaChannelWithCurrentBrush = false) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the position of the bounding box for the current clipping region.
|
||||
@see clipRegionIntersects
|
||||
*/
|
||||
Rectangle<int> getClipBounds() const;
|
||||
|
||||
/** Checks whether a rectangle overlaps the context's clipping region.
|
||||
|
||||
If this returns false, no part of the given area can be drawn onto, so this
|
||||
method can be used to optimise a component's paint() method, by letting it
|
||||
avoid drawing complex objects that aren't within the region being repainted.
|
||||
*/
|
||||
bool clipRegionIntersects (Rectangle<int> area) const;
|
||||
|
||||
/** Intersects the current clipping region with another region.
|
||||
|
||||
@returns true if the resulting clipping region is non-zero in size
|
||||
@see setOrigin, clipRegionIntersects
|
||||
*/
|
||||
bool reduceClipRegion (int x, int y, int width, int height);
|
||||
|
||||
/** Intersects the current clipping region with another region.
|
||||
|
||||
@returns true if the resulting clipping region is non-zero in size
|
||||
@see setOrigin, clipRegionIntersects
|
||||
*/
|
||||
bool reduceClipRegion (Rectangle<int> area);
|
||||
|
||||
/** Intersects the current clipping region with a rectangle list region.
|
||||
|
||||
@returns true if the resulting clipping region is non-zero in size
|
||||
@see setOrigin, clipRegionIntersects
|
||||
*/
|
||||
bool reduceClipRegion (const RectangleList<int>& clipRegion);
|
||||
|
||||
/** Intersects the current clipping region with a path.
|
||||
|
||||
@returns true if the resulting clipping region is non-zero in size
|
||||
@see reduceClipRegion
|
||||
*/
|
||||
bool reduceClipRegion (const Path& path, const AffineTransform& transform = AffineTransform());
|
||||
|
||||
/** Intersects the current clipping region with an image's alpha-channel.
|
||||
|
||||
The current clipping path is intersected with the area covered by this image's
|
||||
alpha-channel, after the image has been transformed by the specified matrix.
|
||||
|
||||
@param image the image whose alpha-channel should be used. If the image doesn't
|
||||
have an alpha-channel, it is treated as entirely opaque.
|
||||
@param transform a matrix to apply to the image
|
||||
@returns true if the resulting clipping region is non-zero in size
|
||||
@see reduceClipRegion
|
||||
*/
|
||||
bool reduceClipRegion (const Image& image, const AffineTransform& transform);
|
||||
|
||||
/** Excludes a rectangle to stop it being drawn into. */
|
||||
void excludeClipRegion (Rectangle<int> rectangleToExclude);
|
||||
|
||||
/** Returns true if no drawing can be done because the clip region is zero. */
|
||||
bool isClipEmpty() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Saves the current graphics state on an internal stack.
|
||||
To restore the state, use restoreState().
|
||||
@see ScopedSaveState
|
||||
*/
|
||||
void saveState();
|
||||
|
||||
/** Restores a graphics state that was previously saved with saveState().
|
||||
@see ScopedSaveState
|
||||
*/
|
||||
void restoreState();
|
||||
|
||||
/** Uses RAII to save and restore the state of a graphics context.
|
||||
On construction, this calls Graphics::saveState(), and on destruction it calls
|
||||
Graphics::restoreState() on the Graphics object that you supply.
|
||||
*/
|
||||
class ScopedSaveState
|
||||
{
|
||||
public:
|
||||
ScopedSaveState (Graphics&);
|
||||
~ScopedSaveState();
|
||||
|
||||
private:
|
||||
Graphics& context;
|
||||
JUCE_DECLARE_NON_COPYABLE (ScopedSaveState)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Begins rendering to an off-screen bitmap which will later be flattened onto the current
|
||||
context with the given opacity.
|
||||
|
||||
The context uses an internal stack of temporary image layers to do this. When you've
|
||||
finished drawing to the layer, call endTransparencyLayer() to complete the operation and
|
||||
composite the finished layer. Every call to beginTransparencyLayer() MUST be matched
|
||||
by a corresponding call to endTransparencyLayer()!
|
||||
|
||||
This call also saves the current state, and endTransparencyLayer() restores it.
|
||||
*/
|
||||
void beginTransparencyLayer (float layerOpacity);
|
||||
|
||||
/** Completes a drawing operation to a temporary semi-transparent buffer.
|
||||
See beginTransparencyLayer() for more details.
|
||||
*/
|
||||
void endTransparencyLayer();
|
||||
|
||||
/** Moves the position of the context's origin.
|
||||
|
||||
This changes the position that the context considers to be (0, 0) to
|
||||
the specified position.
|
||||
|
||||
So if you call setOrigin with (100, 100), then the position that was previously
|
||||
referred to as (100, 100) will subsequently be considered to be (0, 0).
|
||||
|
||||
@see reduceClipRegion, addTransform
|
||||
*/
|
||||
void setOrigin (Point<int> newOrigin);
|
||||
|
||||
/** Moves the position of the context's origin.
|
||||
|
||||
This changes the position that the context considers to be (0, 0) to
|
||||
the specified position.
|
||||
|
||||
So if you call setOrigin (100, 100), then the position that was previously
|
||||
referred to as (100, 100) will subsequently be considered to be (0, 0).
|
||||
|
||||
@see reduceClipRegion, addTransform
|
||||
*/
|
||||
void setOrigin (int newOriginX, int newOriginY);
|
||||
|
||||
/** Adds a transformation which will be performed on all the graphics operations that
|
||||
the context subsequently performs.
|
||||
|
||||
After calling this, all the coordinates that are passed into the context will be
|
||||
transformed by this matrix.
|
||||
|
||||
@see setOrigin
|
||||
*/
|
||||
void addTransform (const AffineTransform& transform);
|
||||
|
||||
/** Resets the current colour, brush, and font to default settings. */
|
||||
void resetToDefaultState();
|
||||
|
||||
/** Returns true if this context is drawing to a vector-based device, such as a printer. */
|
||||
bool isVectorDevice() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Create a graphics that draws with a given low-level renderer.
|
||||
This method is intended for use only by people who know what they're doing.
|
||||
Note that the LowLevelGraphicsContext will NOT be deleted by this object.
|
||||
*/
|
||||
Graphics (LowLevelGraphicsContext&) noexcept;
|
||||
|
||||
/** @internal */
|
||||
LowLevelGraphicsContext& getInternalContext() const noexcept { return context; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
std::unique_ptr<LowLevelGraphicsContext> contextHolder;
|
||||
LowLevelGraphicsContext& context;
|
||||
|
||||
bool saveStatePending = false;
|
||||
void saveStateIfPending();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Graphics)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Interface class for graphics context objects, used internally by the Graphics class.
|
||||
|
||||
Users are not supposed to create instances of this class directly - do your drawing
|
||||
via the Graphics object instead.
|
||||
|
||||
It's a base class for different types of graphics context, that may perform software-based
|
||||
or OS-accelerated rendering.
|
||||
|
||||
E.g. the LowLevelGraphicsSoftwareRenderer renders onto an image in memory, but other
|
||||
subclasses could render directly to a windows HDC, a Quartz context, or an OpenGL
|
||||
context.
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API LowLevelGraphicsContext
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
LowLevelGraphicsContext() = default;
|
||||
|
||||
public:
|
||||
virtual ~LowLevelGraphicsContext() = default;
|
||||
|
||||
/** Returns true if this device is vector-based, e.g. a printer. */
|
||||
virtual bool isVectorDevice() const = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Moves the origin to a new position.
|
||||
|
||||
The coordinates are relative to the current origin, and indicate the new position
|
||||
of (0, 0).
|
||||
*/
|
||||
virtual void setOrigin (Point<int>) = 0;
|
||||
virtual void addTransform (const AffineTransform&) = 0;
|
||||
virtual float getPhysicalPixelScaleFactor() const = 0;
|
||||
|
||||
virtual bool clipToRectangle (const Rectangle<int>&) = 0;
|
||||
virtual bool clipToRectangleList (const RectangleList<int>&) = 0;
|
||||
virtual void excludeClipRectangle (const Rectangle<int>&) = 0;
|
||||
virtual void clipToPath (const Path&, const AffineTransform&) = 0;
|
||||
virtual void clipToImageAlpha (const Image&, const AffineTransform&) = 0;
|
||||
|
||||
virtual bool clipRegionIntersects (const Rectangle<int>&) = 0;
|
||||
virtual Rectangle<int> getClipBounds() const = 0;
|
||||
virtual bool isClipEmpty() const = 0;
|
||||
|
||||
virtual void saveState() = 0;
|
||||
virtual void restoreState() = 0;
|
||||
|
||||
virtual void beginTransparencyLayer (float opacity) = 0;
|
||||
virtual void endTransparencyLayer() = 0;
|
||||
|
||||
//==============================================================================
|
||||
virtual void setFill (const FillType&) = 0;
|
||||
virtual void setOpacity (float) = 0;
|
||||
virtual void setInterpolationQuality (Graphics::ResamplingQuality) = 0;
|
||||
|
||||
//==============================================================================
|
||||
virtual void fillAll() { fillRect (getClipBounds(), false); }
|
||||
virtual void fillRect (const Rectangle<int>&, bool replaceExistingContents) = 0;
|
||||
virtual void fillRect (const Rectangle<float>&) = 0;
|
||||
virtual void fillRectList (const RectangleList<float>&) = 0;
|
||||
virtual void fillPath (const Path&, const AffineTransform&) = 0;
|
||||
|
||||
virtual void drawRect (const Rectangle<float>& rect, float lineThickness)
|
||||
{
|
||||
auto r = rect;
|
||||
RectangleList<float> rects;
|
||||
rects.addWithoutMerging (r.removeFromTop (lineThickness));
|
||||
rects.addWithoutMerging (r.removeFromBottom (lineThickness));
|
||||
rects.addWithoutMerging (r.removeFromLeft (lineThickness));
|
||||
rects.addWithoutMerging (r.removeFromRight (lineThickness));
|
||||
fillRectList (rects);
|
||||
}
|
||||
|
||||
virtual void strokePath (const Path& path, const PathStrokeType& strokeType, const AffineTransform& transform)
|
||||
{
|
||||
Path stroke;
|
||||
strokeType.createStrokedPath (stroke, path, transform, getPhysicalPixelScaleFactor());
|
||||
fillPath (stroke, {});
|
||||
}
|
||||
|
||||
virtual void drawImage (const Image&, const AffineTransform&) = 0;
|
||||
virtual void drawLine (const Line<float>&) = 0;
|
||||
|
||||
virtual void drawLineWithThickness (const Line<float>& line, float lineThickness)
|
||||
{
|
||||
Path p;
|
||||
p.addLineSegment (line, lineThickness);
|
||||
fillPath (p, {});
|
||||
}
|
||||
|
||||
virtual void setFont (const Font&) = 0;
|
||||
virtual const Font& getFont() = 0;
|
||||
|
||||
/** Uses the current font to draw the provided glyph numbers. */
|
||||
virtual void drawGlyphs (Span<const uint16_t>,
|
||||
Span<const Point<float>>,
|
||||
const AffineTransform&) = 0;
|
||||
|
||||
virtual void drawRoundedRectangle (const Rectangle<float>& r, float cornerSize, float lineThickness)
|
||||
{
|
||||
Path p;
|
||||
p.addRoundedRectangle (r, cornerSize);
|
||||
strokePath (p, PathStrokeType (lineThickness), {});
|
||||
}
|
||||
|
||||
virtual void fillRoundedRectangle (const Rectangle<float>& r, float cornerSize)
|
||||
{
|
||||
Path p;
|
||||
p.addRoundedRectangle (r, cornerSize);
|
||||
fillPath (p, {});
|
||||
}
|
||||
|
||||
virtual void drawEllipse (const Rectangle<float>& area, float lineThickness)
|
||||
{
|
||||
Path p;
|
||||
|
||||
if (approximatelyEqual (area.getWidth(), area.getHeight()))
|
||||
{
|
||||
// For a circle, we can avoid having to generate a stroke
|
||||
p.addEllipse (area.expanded (lineThickness * 0.5f));
|
||||
p.addEllipse (area.reduced (lineThickness * 0.5f));
|
||||
p.setUsingNonZeroWinding (false);
|
||||
fillPath (p, {});
|
||||
}
|
||||
else
|
||||
{
|
||||
p.addEllipse (area);
|
||||
strokePath (p, PathStrokeType (lineThickness), {});
|
||||
}
|
||||
}
|
||||
|
||||
virtual void fillEllipse (const Rectangle<float>& area)
|
||||
{
|
||||
Path p;
|
||||
p.addEllipse (area);
|
||||
fillPath (p, {});
|
||||
}
|
||||
|
||||
/** Returns an integer that uniquely identifies the current frame.
|
||||
Useful for debugging/logging.
|
||||
*/
|
||||
virtual uint64_t getFrameId() const = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image)
|
||||
: RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState>
|
||||
(new RenderingHelpers::SoftwareRendererSavedState (image, image.getBounds()))
|
||||
{
|
||||
JUCE_TRACE_LOG_PAINT_CALL (etw::startGDIImage, getFrameId());
|
||||
}
|
||||
|
||||
LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image, Point<int> origin,
|
||||
const RectangleList<int>& initialClip)
|
||||
: RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState>
|
||||
(new RenderingHelpers::SoftwareRendererSavedState (image, initialClip, origin))
|
||||
{
|
||||
|
||||
JUCE_TRACE_EVENT_INT_RECT_LIST (etw::startGDIFrame, etw::softwareRendererKeyword, getFrameId(), initialClip);
|
||||
}
|
||||
|
||||
LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer()
|
||||
{
|
||||
JUCE_TRACE_LOG_PAINT_CALL (etw::endGDIFrame, getFrameId());
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A lowest-common-denominator implementation of LowLevelGraphicsContext that does all
|
||||
its rendering in memory.
|
||||
|
||||
User code is not supposed to create instances of this class directly - do all your
|
||||
rendering via the Graphics class instead.
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API LowLevelGraphicsSoftwareRenderer : public RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState>
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a context to render into an image. */
|
||||
LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto);
|
||||
|
||||
/** Creates a context to render into a clipped subsection of an image. */
|
||||
LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto, Point<int> origin,
|
||||
const RectangleList<int>& initialClip);
|
||||
|
||||
/** Destructor. */
|
||||
~LowLevelGraphicsSoftwareRenderer() override;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LowLevelGraphicsSoftwareRenderer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,627 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
void drawJustifiedText (const JustifiedText& text, const Graphics& g, AffineTransform);
|
||||
|
||||
//==============================================================================
|
||||
static constexpr auto maxWidthTolerance = 0.005f;
|
||||
|
||||
static int64 getNumLeadingWhitespaces (Span<const ShapedGlyph> glyphs)
|
||||
{
|
||||
const auto it = std::find_if_not (glyphs.begin(),
|
||||
glyphs.end(),
|
||||
[&] (const auto& g) { return g.isWhitespace(); });
|
||||
|
||||
return (int64) std::distance (glyphs.begin(), it);
|
||||
}
|
||||
|
||||
static int64 getNumTrailingWhitespaces (Span<const ShapedGlyph> glyphs)
|
||||
{
|
||||
if (glyphs.empty())
|
||||
return 0;
|
||||
|
||||
int64 trailingWhitespaces = 0;
|
||||
|
||||
for (auto it = glyphs.end(); --it >= glyphs.begin() && it->isWhitespace();)
|
||||
++trailingWhitespaces;
|
||||
|
||||
return trailingWhitespaces;
|
||||
}
|
||||
|
||||
struct NumWhitespaces
|
||||
{
|
||||
int64 total{}, leading{}, trailing{};
|
||||
};
|
||||
|
||||
static NumWhitespaces getNumWhitespaces (Span<const ShapedGlyph> glyphs)
|
||||
{
|
||||
const auto total = std::count_if (glyphs.begin(),
|
||||
glyphs.end(),
|
||||
[] (const auto& g) { return g.isWhitespace(); });
|
||||
|
||||
return { total, getNumLeadingWhitespaces (glyphs), getNumTrailingWhitespaces (glyphs) };
|
||||
}
|
||||
|
||||
struct LineLength
|
||||
{
|
||||
float total{}, withoutTrailingWhitespaces{};
|
||||
};
|
||||
|
||||
static LineLength getMainAxisLineLength (Span<const ShapedGlyph> glyphs)
|
||||
{
|
||||
const auto total = std::accumulate (glyphs.begin(),
|
||||
glyphs.end(),
|
||||
0.0f,
|
||||
[] (auto acc, const auto& g) { return acc + g.advance.getX(); });
|
||||
|
||||
auto trailingWhitespacesLength = 0.0f;
|
||||
|
||||
if (glyphs.empty())
|
||||
return {};
|
||||
|
||||
for (auto it = glyphs.end(); --it >= glyphs.begin() && it->isWhitespace();)
|
||||
trailingWhitespacesLength += it->advance.getX();
|
||||
|
||||
return { total, total - trailingWhitespacesLength };
|
||||
}
|
||||
|
||||
static float getMainAxisLineLength (Span<const ShapedGlyph> glyphs, bool trailingWhitespacesShouldFit)
|
||||
{
|
||||
const auto lengths = getMainAxisLineLength (glyphs);
|
||||
|
||||
return trailingWhitespacesShouldFit ? lengths.total : lengths.withoutTrailingWhitespaces;
|
||||
}
|
||||
|
||||
struct MainAxisLineAlignment
|
||||
{
|
||||
float anchor{}, extraWhitespaceAdvance{}, effectiveLineLength{};
|
||||
Range<int64> stretchableWhitespaces;
|
||||
};
|
||||
|
||||
static MainAxisLineAlignment getMainAxisLineAlignment (Justification justification,
|
||||
Span<const ShapedGlyph> glyphs,
|
||||
LineLength lineLength,
|
||||
std::optional<float> maxWidthOpt,
|
||||
std::optional<float> alignmentWidthOpt,
|
||||
bool trailingWhitespacesShouldFit)
|
||||
{
|
||||
const auto effectiveLineLength = (trailingWhitespacesShouldFit ? lineLength.total
|
||||
: lineLength.withoutTrailingWhitespaces);
|
||||
|
||||
const auto alignmentWidth = alignmentWidthOpt.value_or (maxWidthOpt.value_or (0.0f));
|
||||
const auto tooLong = alignmentWidth + maxWidthTolerance < effectiveLineLength;
|
||||
|
||||
MainAxisLineAlignment result;
|
||||
result.effectiveLineLength = effectiveLineLength;
|
||||
|
||||
// The alignment width opt is supporting the TextEditor use-case where all text remains visible
|
||||
// with scrolling, even if longer than the alignment width. We don't need to worry about the
|
||||
// front of and RTL text being visually truncated, because nothing is truncated.
|
||||
if (tooLong && alignmentWidthOpt.has_value())
|
||||
return result;
|
||||
|
||||
const auto mainAxisLineOffset = [&]
|
||||
{
|
||||
if (tooLong)
|
||||
{
|
||||
const auto approximateIsLeftToRight = [&]
|
||||
{
|
||||
if (glyphs.empty())
|
||||
return true;
|
||||
|
||||
return glyphs.front().cluster <= glyphs.back().cluster;
|
||||
}();
|
||||
|
||||
// We don't have to align LTR text, but we need to ensure that it's the logical back of
|
||||
// RTL text that falls outside the bounds.
|
||||
if (approximateIsLeftToRight)
|
||||
return 0.0f;
|
||||
|
||||
return alignmentWidth - effectiveLineLength;
|
||||
}
|
||||
|
||||
if (justification.testFlags (Justification::horizontallyCentred))
|
||||
{
|
||||
return (alignmentWidth - lineLength.withoutTrailingWhitespaces) / 2.0f;
|
||||
}
|
||||
|
||||
if (justification.testFlags (Justification::right))
|
||||
return alignmentWidth - effectiveLineLength;
|
||||
|
||||
return 0.0f;
|
||||
}();
|
||||
|
||||
const auto numWhitespaces = getNumWhitespaces (glyphs);
|
||||
|
||||
const auto stretchableWhitespaces = [&]() -> Range<int64>
|
||||
{
|
||||
if (! justification.testFlags (Justification::horizontallyJustified) || tooLong)
|
||||
return {};
|
||||
|
||||
return { numWhitespaces.leading, (int64) glyphs.size() - numWhitespaces.trailing };
|
||||
}();
|
||||
|
||||
const auto extraWhitespaceAdvance = [&]
|
||||
{
|
||||
if (! justification.testFlags (Justification::horizontallyJustified) || tooLong)
|
||||
return 0.0f;
|
||||
|
||||
const auto numWhitespacesBetweenWords = numWhitespaces.total
|
||||
- numWhitespaces.leading
|
||||
- numWhitespaces.trailing;
|
||||
|
||||
return numWhitespacesBetweenWords > 0 ? (alignmentWidth - effectiveLineLength) / (float) numWhitespacesBetweenWords
|
||||
: 0.0f;
|
||||
}();
|
||||
|
||||
return { mainAxisLineOffset, extraWhitespaceAdvance, effectiveLineLength, stretchableWhitespaces };
|
||||
}
|
||||
|
||||
struct LineInfo
|
||||
{
|
||||
float lineHeight{}, maxAscent{};
|
||||
MainAxisLineAlignment mainAxisLineAlignment;
|
||||
};
|
||||
|
||||
static float getCrossAxisStartingAnchor (Justification justification,
|
||||
Span<const LineInfo> lineInfos,
|
||||
std::optional<float> height,
|
||||
float leadingInHeight)
|
||||
{
|
||||
if (lineInfos.empty())
|
||||
return 0.0f;
|
||||
|
||||
const auto minimumTop = lineInfos.front().maxAscent + lineInfos.front().lineHeight * leadingInHeight;
|
||||
|
||||
if (! height.has_value())
|
||||
return minimumTop;
|
||||
|
||||
const auto textHeight = std::accumulate (lineInfos.begin(),
|
||||
lineInfos.end(),
|
||||
0.0f,
|
||||
[] (auto acc, const auto info) { return acc + info.lineHeight; });
|
||||
|
||||
if (justification.testFlags (Justification::verticallyCentred))
|
||||
return (*height - textHeight) / 2.0f + lineInfos.front().maxAscent;
|
||||
|
||||
if (justification.testFlags (Justification::bottom))
|
||||
{
|
||||
const auto bottomLeading = 0.5f * lineInfos.back().lineHeight * leadingInHeight;
|
||||
return *height - textHeight - bottomLeading + lineInfos.front().maxAscent;
|
||||
}
|
||||
|
||||
return minimumTop;
|
||||
}
|
||||
|
||||
JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions& options)
|
||||
: shapedText (*t)
|
||||
{
|
||||
const auto leading = options.getLeading() - 1.0f;
|
||||
|
||||
std::vector<LineInfo> lineInfos;
|
||||
|
||||
for (const auto [range, lineNumber] : shapedText.getLineNumbersForGlyphRanges())
|
||||
{
|
||||
// This is guaranteed by the RangedValues implementation. You can't assign a value to an
|
||||
// empty range.
|
||||
jassert (! range.isEmpty());
|
||||
|
||||
const auto fonts = shapedText.getResolvedFonts().getIntersectionsWith (range);
|
||||
|
||||
const auto lineHeight = std::accumulate (fonts.begin(),
|
||||
fonts.end(),
|
||||
0.0f,
|
||||
[] (auto acc, const auto& rangedFont)
|
||||
{ return std::max (acc, rangedFont.value.getHeight()); });
|
||||
|
||||
const auto maxAscent = std::accumulate (fonts.begin(),
|
||||
fonts.end(),
|
||||
0.0f,
|
||||
[] (auto acc, const auto& rangedFont)
|
||||
{ return std::max (acc, rangedFont.value.getAscent()); });
|
||||
|
||||
const auto glyphs = shapedText.getGlyphs (range);
|
||||
const auto lineLength = getMainAxisLineLength (glyphs);
|
||||
|
||||
auto m = [&]
|
||||
{
|
||||
return getMainAxisLineAlignment (options.getJustification(),
|
||||
glyphs,
|
||||
lineLength,
|
||||
options.getMaxWidth(),
|
||||
options.getAlignmentWidth(),
|
||||
options.getTrailingWhitespacesShouldFit());
|
||||
}();
|
||||
|
||||
const auto containsHardBreak = shapedText.getCodepoint (range.getEnd() - 1) == 0xa
|
||||
|| shapedText.getCodepoint (range.getStart()) == 0xa;
|
||||
|
||||
if (containsHardBreak || lineNumber == shapedText.getLineNumbersForGlyphRanges().back().value)
|
||||
{
|
||||
m.extraWhitespaceAdvance = {};
|
||||
m.stretchableWhitespaces = {};
|
||||
}
|
||||
|
||||
lineInfos.push_back ({ lineHeight, maxAscent, std::move (m) });
|
||||
minimumRequiredWidthsForLine.push_back (options.getTrailingWhitespacesShouldFit() ? lineLength.total
|
||||
: lineLength.withoutTrailingWhitespaces);
|
||||
}
|
||||
|
||||
auto baseline = options.isBaselineAtZero() ? 0.0f
|
||||
: getCrossAxisStartingAnchor (options.getJustification(),
|
||||
lineInfos,
|
||||
options.getHeight(),
|
||||
leading);
|
||||
|
||||
detail::Ranges::Operations ops;
|
||||
|
||||
std::optional<float> top;
|
||||
|
||||
for (const auto [lineIndex, lineInfo] : enumerate (lineInfos))
|
||||
{
|
||||
const auto lineNumber = shapedText.getLineNumbersForGlyphRanges().getItem ((size_t) lineIndex);
|
||||
const auto range = lineNumber.range;
|
||||
|
||||
const auto maxDescent = lineInfo.lineHeight - lineInfo.maxAscent;
|
||||
const auto nextLineTop = baseline + (1.0f + leading) * maxDescent + options.getAdditiveLineSpacing();
|
||||
|
||||
if (! top.has_value())
|
||||
top = baseline - (1.0f + leading) * lineInfo.maxAscent;
|
||||
|
||||
lineMetricsForGlyphRange.set (range,
|
||||
{ lineNumber.value,
|
||||
{ lineInfo.mainAxisLineAlignment.anchor, baseline },
|
||||
lineInfo.maxAscent,
|
||||
lineInfo.lineHeight - lineInfo.maxAscent,
|
||||
lineInfo.mainAxisLineAlignment.effectiveLineLength
|
||||
+ lineInfo.mainAxisLineAlignment.extraWhitespaceAdvance,
|
||||
*top,
|
||||
nextLineTop },
|
||||
ops,
|
||||
MergeEqualItemsNo{});
|
||||
|
||||
whitespaceStretch.set (range, 0.0f, ops);
|
||||
const auto stretchRange = lineInfo.mainAxisLineAlignment.stretchableWhitespaces + range.getStart();
|
||||
|
||||
whitespaceStretch.set (stretchRange,
|
||||
lineInfo.mainAxisLineAlignment.extraWhitespaceAdvance,
|
||||
ops);
|
||||
|
||||
ops.clear();
|
||||
|
||||
const auto nextLineMaxAscent = lineIndex < (int) lineInfos.size() - 1 ? lineInfos[(size_t) lineIndex + 1].maxAscent : 0.0f;
|
||||
baseline = nextLineTop + (1.0f + leading) * nextLineMaxAscent;
|
||||
top = nextLineTop;
|
||||
}
|
||||
|
||||
rangesToDraw.set ({ 0, (int64) shapedText.getGlyphs().size() }, DrawType::normal, ops);
|
||||
|
||||
//==============================================================================
|
||||
// Everything above this line should work well given none of the lines were too
|
||||
// long. When Options::getMaxNumLines() == 0 this is guaranteed by SimpleShapedText.
|
||||
// The remaining logic below is for supporting
|
||||
// GlyphArrangement::addFittedText() when the maximum number of lines is
|
||||
// constrained.
|
||||
if (lineMetricsForGlyphRange.isEmpty())
|
||||
return;
|
||||
|
||||
const auto lastLineMetrics = lineMetricsForGlyphRange.back();
|
||||
const auto lastLineGlyphRange = lastLineMetrics.range;
|
||||
const auto lastLineGlyphs = shapedText.getGlyphs (lastLineGlyphRange);
|
||||
const auto lastLineLengths = getMainAxisLineLength (lastLineGlyphs);
|
||||
|
||||
const auto effectiveLength = options.getTrailingWhitespacesShouldFit() ? lastLineLengths.total
|
||||
: lastLineLengths.withoutTrailingWhitespaces;
|
||||
|
||||
if (! options.getMaxWidth().has_value()
|
||||
|| effectiveLength <= *options.getMaxWidth() + maxWidthTolerance)
|
||||
return;
|
||||
|
||||
const auto cutoffAtFront = lastLineMetrics.value.anchor.getX() < 0.0f - maxWidthTolerance;
|
||||
|
||||
const auto getLastLineVisibleRange = [&] (float ellipsisLength)
|
||||
{
|
||||
const auto r = [&]() -> Range<int64>
|
||||
{
|
||||
if (cutoffAtFront)
|
||||
{
|
||||
auto length = lastLineLengths.total;
|
||||
|
||||
for (auto it = lastLineGlyphs.begin(); it < lastLineGlyphs.end(); ++it)
|
||||
{
|
||||
length -= it->advance.getX();
|
||||
|
||||
if (! options.getMaxWidth().has_value()
|
||||
|| *options.getMaxWidth() >= ellipsisLength + length)
|
||||
{
|
||||
return { (int64) std::distance (lastLineGlyphs.begin(), it) + 1,
|
||||
(int64) lastLineGlyphs.size() };
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto length = lastLineLengths.total;
|
||||
|
||||
for (auto it = lastLineGlyphs.end() - 1; it >= lastLineGlyphs.begin(); --it)
|
||||
{
|
||||
length -= it->advance.getX();
|
||||
|
||||
if (! options.getMaxWidth().has_value()
|
||||
|| *options.getMaxWidth() >= ellipsisLength + length)
|
||||
{
|
||||
return { 0, (int64) std::distance (lastLineGlyphs.begin(), it) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
return r.movedToStartAt (r.getStart() + lastLineGlyphRange.getStart());
|
||||
};
|
||||
|
||||
const auto lastLineVisibleRangeWithoutEllipsis = getLastLineVisibleRange (0.0f);
|
||||
|
||||
const auto eraseLastLineFromRangesToDraw = [&]
|
||||
{
|
||||
rangesToDraw.eraseFrom (lastLineGlyphRange.getStart(), ops);
|
||||
ops.clear();
|
||||
};
|
||||
|
||||
eraseLastLineFromRangesToDraw();
|
||||
rangesToDraw.set (lastLineVisibleRangeWithoutEllipsis, DrawType::normal, ops);
|
||||
ops.clear();
|
||||
|
||||
if (options.getEllipsis().isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// More logic supporting using ellipses
|
||||
const auto fontForEllipsis = [&]
|
||||
{
|
||||
const auto lastLineFonts = shapedText.getResolvedFonts().getIntersectionsWith (lastLineGlyphRange);
|
||||
|
||||
if (cutoffAtFront)
|
||||
return lastLineFonts.front().value;
|
||||
|
||||
return lastLineFonts.back().value;
|
||||
}();
|
||||
|
||||
ellipsis.emplace (&options.getEllipsis(), ShapedTextOptions {}.withFont (fontForEllipsis));
|
||||
|
||||
const auto lastLineVisibleRange = getLastLineVisibleRange (getMainAxisLineLength (ellipsis->getGlyphs(),
|
||||
options.getTrailingWhitespacesShouldFit()));
|
||||
|
||||
eraseLastLineFromRangesToDraw();
|
||||
rangesToDraw.set (lastLineVisibleRange, DrawType::normal, ops);
|
||||
ops.clear();
|
||||
|
||||
if (cutoffAtFront)
|
||||
rangesToDraw.set (Range<int64>::withStartAndLength (lastLineVisibleRange.getStart() - 1, 1), DrawType::ellipsis, ops);
|
||||
else
|
||||
rangesToDraw.set (Range<int64>::withStartAndLength (lastLineVisibleRange.getEnd(), 1), DrawType::ellipsis, ops);
|
||||
|
||||
ops.clear();
|
||||
|
||||
const auto lineWithEllipsisGlyphs = [&]
|
||||
{
|
||||
std::vector<ShapedGlyph> result;
|
||||
|
||||
const auto pushEllipsisGlyphs = [&]
|
||||
{
|
||||
const auto& range = ellipsis->getGlyphs();
|
||||
result.insert (result.begin(), range.begin(), range.end());
|
||||
};
|
||||
|
||||
if (cutoffAtFront)
|
||||
pushEllipsisGlyphs();
|
||||
|
||||
const auto& range = shapedText.getGlyphs (lastLineVisibleRange);
|
||||
result.insert (result.end(), range.begin(), range.end());
|
||||
|
||||
if (! cutoffAtFront)
|
||||
pushEllipsisGlyphs();
|
||||
|
||||
return result;
|
||||
}();
|
||||
|
||||
const auto realign = [&]
|
||||
{
|
||||
return getMainAxisLineAlignment (options.getJustification(),
|
||||
lineWithEllipsisGlyphs,
|
||||
getMainAxisLineLength (lineWithEllipsisGlyphs),
|
||||
options.getMaxWidth(),
|
||||
options.getAlignmentWidth(),
|
||||
options.getTrailingWhitespacesShouldFit());
|
||||
}();
|
||||
|
||||
lastLineMetrics.value.anchor.setX (realign.anchor);
|
||||
whitespaceStretch.set (lastLineGlyphRange, 0.0f, ops);
|
||||
whitespaceStretch.set (realign.stretchableWhitespaces + lastLineVisibleRange.getStart(),
|
||||
realign.extraWhitespaceAdvance, ops);
|
||||
}
|
||||
|
||||
int64 JustifiedText::getGlyphIndexToTheRightOf (Point<float> p) const
|
||||
{
|
||||
auto lineIt = lineMetricsForGlyphRange.begin();
|
||||
float lineTop = 0.0f;
|
||||
|
||||
while (lineIt != lineMetricsForGlyphRange.end())
|
||||
{
|
||||
const auto nextLineTop = lineIt->value.nextLineTop;
|
||||
|
||||
if (lineTop <= p.getY() && p.getY() < nextLineTop)
|
||||
break;
|
||||
|
||||
lineTop = nextLineTop;
|
||||
++lineIt;
|
||||
}
|
||||
|
||||
if (lineIt == lineMetricsForGlyphRange.end())
|
||||
return 0;
|
||||
|
||||
const auto glyphsInLine = shapedText.getGlyphs (lineIt->range);
|
||||
|
||||
auto glyphIndex = lineIt->range.getStart();
|
||||
auto glyphX = lineIt->value.anchor.getX();
|
||||
|
||||
for (const auto& glyph : glyphsInLine)
|
||||
{
|
||||
if ( p.getX() < glyphX + glyph.advance.getX() / 2.0f
|
||||
|| glyph.isNewline()
|
||||
|| (glyphIndex - lineIt->range.getStart() == (int64) glyphsInLine.size() - 1 && glyph.isWhitespace()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
++glyphIndex;
|
||||
glyphX += glyph.advance.getX();
|
||||
}
|
||||
|
||||
return glyphIndex;
|
||||
}
|
||||
|
||||
GlyphAnchorResult JustifiedText::getGlyphAnchor (int64 index) const
|
||||
{
|
||||
jassert (index >= 0);
|
||||
|
||||
if (lineMetricsForGlyphRange.isEmpty())
|
||||
return {};
|
||||
|
||||
const auto lineItem = lineMetricsForGlyphRange.getItemWithEnclosingRange (index)
|
||||
.value_or (lineMetricsForGlyphRange.back());
|
||||
|
||||
const auto indexInLine = index - lineItem.range.getStart();
|
||||
|
||||
GlyphAnchorResult anchor { lineItem.value.anchor, lineItem.value.maxAscent, lineItem.value.maxDescent };
|
||||
|
||||
for (auto [i, glyph] : enumerate (shapedText.getGlyphs (lineItem.range), int64{}))
|
||||
{
|
||||
if (i == indexInLine)
|
||||
{
|
||||
anchor.anchor += glyph.offset;
|
||||
return anchor;
|
||||
}
|
||||
|
||||
anchor.anchor += glyph.advance;
|
||||
}
|
||||
|
||||
return anchor;
|
||||
}
|
||||
|
||||
RectangleList<float> JustifiedText::getGlyphsBounds (Range<int64> glyphRange) const
|
||||
{
|
||||
RectangleList<float> bounds;
|
||||
|
||||
if (lineMetricsForGlyphRange.isEmpty())
|
||||
return bounds;
|
||||
|
||||
const auto getBounds = [&] (const LineMetrics& line, int64 lineStart, int64 boundsStart, int64 boundsEnd) -> Rectangle<float>
|
||||
{
|
||||
const auto glyphsBefore = shapedText.getGlyphs ({ lineStart, boundsStart });
|
||||
|
||||
const auto xStart = std::accumulate (glyphsBefore.begin(),
|
||||
glyphsBefore.end(),
|
||||
line.anchor.getX(),
|
||||
[] (auto sum, auto glyph)
|
||||
{
|
||||
return sum + glyph.advance.getX();
|
||||
});
|
||||
|
||||
const auto glyphs = shapedText.getGlyphs ({ boundsStart, boundsEnd });
|
||||
|
||||
const auto xEnd = std::accumulate (glyphs.begin(),
|
||||
glyphs.end(),
|
||||
xStart,
|
||||
[&] (auto sum, auto glyph)
|
||||
{
|
||||
return sum + glyph.advance.getX();
|
||||
});
|
||||
|
||||
return { { xStart, line.top }, { xEnd, line.nextLineTop } };
|
||||
};
|
||||
|
||||
for (auto consumeFrom = glyphRange.getStart(); consumeFrom < glyphRange.getEnd();)
|
||||
{
|
||||
const auto lineItem = lineMetricsForGlyphRange.getItemWithEnclosingRange (consumeFrom);
|
||||
|
||||
if (! lineItem.has_value())
|
||||
break;
|
||||
|
||||
const auto consumeTo = std::min (glyphRange.getEnd(), lineItem->range.getEnd());
|
||||
bounds.add (getBounds (lineItem->value, lineItem->range.getStart(), consumeFrom, consumeTo));
|
||||
|
||||
consumeFrom = consumeTo;
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
float JustifiedText::getHeight() const
|
||||
{
|
||||
if (lineMetricsForGlyphRange.isEmpty())
|
||||
return 0.0f;
|
||||
|
||||
return lineMetricsForGlyphRange.back().value.nextLineTop;
|
||||
}
|
||||
|
||||
void drawJustifiedText (const JustifiedText& text, const Graphics& g, AffineTransform transform)
|
||||
{
|
||||
auto& context = g.getInternalContext();
|
||||
context.saveState();
|
||||
const ScopeGuard restoreGraphicsContext { [&context] { context.restoreState(); } };
|
||||
|
||||
text.accessTogetherWith ([&] (auto glyphs, auto positions, auto font, auto, auto)
|
||||
{
|
||||
if (context.getFont() != font)
|
||||
context.setFont (font);
|
||||
|
||||
std::vector<uint16_t> glyphIds (glyphs.size());
|
||||
|
||||
std::transform (glyphs.begin(),
|
||||
glyphs.end(),
|
||||
glyphIds.begin(),
|
||||
[] (auto& glyph) { return (uint16_t) glyph.glyphId; });
|
||||
|
||||
context.drawGlyphs (glyphIds, positions, transform);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
template <size_t StartingAt, typename Tuple, size_t... Is>
|
||||
constexpr auto partiallyUnpackImpl (Tuple&& tuple, std::index_sequence<Is...>)
|
||||
{
|
||||
return std::tie (std::get<StartingAt + Is> (tuple)...);
|
||||
}
|
||||
|
||||
template <size_t StartingAt, size_t NumElems, typename Tuple>
|
||||
constexpr auto partiallyUnpack (Tuple&& tuple)
|
||||
{
|
||||
return partiallyUnpackImpl<StartingAt> (std::forward<Tuple> (tuple), std::make_index_sequence<NumElems>{});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct LineMetrics
|
||||
{
|
||||
int64 lineNumber;
|
||||
Point<float> anchor;
|
||||
float maxAscent;
|
||||
float maxDescent;
|
||||
|
||||
/* Effective suggests the length of trailing whitespaces will be included or not depending on
|
||||
ShapedTextOptions::getTrailingWhitespacesShouldFit().
|
||||
*/
|
||||
float effectiveLineLength;
|
||||
|
||||
/* These values seem redundant given the relation between the baseline, ascent and top, but
|
||||
we want to ensure exactlyEquals (top, nextLineTop) for subsequent lines.
|
||||
*/
|
||||
float top;
|
||||
|
||||
/* This will be below the current line's visual bottom if non-default leading or additive
|
||||
line spacing is used.
|
||||
*/
|
||||
float nextLineTop;
|
||||
};
|
||||
|
||||
struct GlyphAnchorResult
|
||||
{
|
||||
Point<float> anchor;
|
||||
float maxAscent{};
|
||||
float maxDescent{};
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class JustifiedText
|
||||
{
|
||||
private:
|
||||
enum class DrawType
|
||||
{
|
||||
normal,
|
||||
ellipsis
|
||||
};
|
||||
|
||||
public:
|
||||
JustifiedText (const SimpleShapedText* t, const ShapedTextOptions& options);
|
||||
|
||||
/* Provides access to the data stored in the ShapedText.
|
||||
|
||||
The provided callable will be called multiple times for "uniform glyph runs", for which all
|
||||
callback parameters are the same.
|
||||
|
||||
Between each subsequent callback at least one of the provided parameters will be different.
|
||||
|
||||
The callbacks happen in visual order i.e. left to right, which is irrespective of the
|
||||
underlying text's writing direction.
|
||||
|
||||
The callback parameters in order are:
|
||||
- the glyphs
|
||||
- the positions for each glyph in the previous parameter
|
||||
- the Font with which these glyphs should be rendered
|
||||
- the range in all glyphs this ShapedText object holds, that correspond to the current glyphs
|
||||
- a line number which increases by one for each new line
|
||||
- followed by any number of ValueType parameters for the supplied RangedValues<ValueType> arguments
|
||||
*/
|
||||
template <typename Callable, typename... RangedValues>
|
||||
void accessTogetherWith (Callable&& callback, RangedValues&&... rangedValues) const
|
||||
{
|
||||
std::optional<int64> lastLine;
|
||||
int64 lastGlyph = 0;
|
||||
Point<float> anchor {};
|
||||
|
||||
for (const auto item : makeIntersectingRangedValues (&shapedText.getResolvedFonts(),
|
||||
&lineMetricsForGlyphRange,
|
||||
&rangesToDraw,
|
||||
&whitespaceStretch,
|
||||
(&rangedValues)...))
|
||||
{
|
||||
const auto& [range, font, lineMetrics, drawType, stretch] = partiallyUnpack<0, 5> (item);
|
||||
const auto& rest = partiallyUnpack<5, std::tuple_size_v<decltype (item)> - 5> (item);
|
||||
|
||||
if (std::exchange (lastLine, lineMetrics.lineNumber) != lineMetrics.lineNumber)
|
||||
anchor = lineMetrics.anchor;
|
||||
|
||||
if (range.getStart() != lastGlyph && drawType != DrawType::ellipsis)
|
||||
{
|
||||
detail::RangedValues<int8_t> glyphMask;
|
||||
Ranges::Operations ops;
|
||||
|
||||
const auto firstGlyphInCurrentLine = shapedText.getLineNumbersForGlyphRanges().getItem ((size_t) lineMetrics.lineNumber)
|
||||
.range
|
||||
.getStart();
|
||||
|
||||
glyphMask.set ({ std::max (lastGlyph, firstGlyphInCurrentLine), range.getStart() }, 1, ops);
|
||||
|
||||
for (const auto [skippedRange, skippedStretch, _] : makeIntersectingRangedValues (&whitespaceStretch,
|
||||
&glyphMask))
|
||||
{
|
||||
ignoreUnused (_);
|
||||
|
||||
for (const auto& skippedGlyph : shapedText.getGlyphs (skippedRange))
|
||||
{
|
||||
anchor += skippedGlyph.advance;
|
||||
|
||||
if (skippedGlyph.isWhitespace())
|
||||
anchor.addXY (skippedStretch, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastGlyph = range.getEnd();
|
||||
|
||||
const auto glyphs = [this, r = range, dt = drawType]() -> Span<const ShapedGlyph>
|
||||
{
|
||||
if (dt == DrawType::ellipsis)
|
||||
return ellipsis->getGlyphs();
|
||||
|
||||
return shapedText.getGlyphs (r);
|
||||
}();
|
||||
|
||||
std::vector<Point<float>> positions (glyphs.size());
|
||||
|
||||
std::transform (glyphs.begin(),
|
||||
glyphs.end(),
|
||||
positions.begin(),
|
||||
[&anchor, &s = stretch] (auto& glyph)
|
||||
{
|
||||
auto result = anchor + glyph.offset;
|
||||
|
||||
anchor += glyph.advance;
|
||||
|
||||
if (glyph.isWhitespace())
|
||||
anchor.addXY (s, 0.0f);
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const auto callbackFont =
|
||||
drawType == DrawType::ellipsis ? ellipsis->getResolvedFonts().front().value : font;
|
||||
const auto callbackParameters =
|
||||
std::tuple_cat (std::tie (glyphs, positions, callbackFont, range, lineMetrics), rest);
|
||||
|
||||
const auto invokeNullChecked = [&] (auto&... params)
|
||||
{ NullCheckedInvocation::invoke (callback, params...); };
|
||||
|
||||
std::apply (invokeNullChecked, callbackParameters);
|
||||
}
|
||||
}
|
||||
|
||||
/* This is how much cumulative widths glyphs take up in each line. Whether the trailing
|
||||
whitespace is included depends on the ShapedTextOptions::getWhitespaceShouldFitInLine()
|
||||
setting.
|
||||
*/
|
||||
auto& getMinimumRequiredWidthForLines() const { return minimumRequiredWidthsForLine; }
|
||||
|
||||
int64 getGlyphIndexToTheRightOf (Point<float> p) const;
|
||||
|
||||
/* If the passed in index parameter is greater than the index of the last contained glyph,
|
||||
then the returned anchor specifies the location where the next glyph would have to be
|
||||
placed i.e. lastGlyphAnchor + lastGlyphAdvance.
|
||||
*/
|
||||
GlyphAnchorResult getGlyphAnchor (int64 glyphIndex) const;
|
||||
|
||||
RectangleList<float> getGlyphsBounds (Range<int64> glyphRange) const;
|
||||
|
||||
/* Returns the vertical distance from the baseline of the first line to the bottom of the last
|
||||
plus any additional line spacing that follows from the leading and additiveLineSpacing
|
||||
members of the ShapedTextOptions object.
|
||||
|
||||
This guarantees that if ShapedText object1 is drawn at y = 0 and object2 is drawn at
|
||||
y = object1.getHeight(), then the two texts will be spaced exactly as if they were a single
|
||||
ShapedText object containing both texts.
|
||||
*/
|
||||
float getHeight() const;
|
||||
|
||||
const auto& getLineMetricsForGlyphRange() const { return lineMetricsForGlyphRange; }
|
||||
|
||||
private:
|
||||
const SimpleShapedText& shapedText;
|
||||
detail::RangedValues<LineMetrics> lineMetricsForGlyphRange;
|
||||
std::optional<SimpleShapedText> ellipsis;
|
||||
detail::RangedValues<DrawType> rangesToDraw;
|
||||
detail::RangedValues<float> whitespaceStretch;
|
||||
std::vector<float> minimumRequiredWidthsForLine;
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,871 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
template <typename T, typename = void>
|
||||
constexpr auto hasGetStartFunction = false;
|
||||
|
||||
template <typename T>
|
||||
constexpr auto hasGetStartFunction<T, std::void_t<decltype (std::declval<T>().getStart())>> = true;
|
||||
|
||||
template <typename RangeType, typename std::enable_if<hasGetStartFunction<RangeType>, int>::type = 0>
|
||||
std::ostream& operator<< (std::ostream& os, const RangeType& range)
|
||||
{
|
||||
os << "[" << range.getStart() << ", " << range.getEnd() << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
static String& operator<< (String& s, Range<int64> r)
|
||||
{
|
||||
return s += "[" + String { r.getStart() } + ", " + String { r.getEnd() } + ")";
|
||||
}
|
||||
|
||||
template <typename ValueType>
|
||||
static auto getCumulativeRangeLengths (const RangedValues<ValueType>& rv)
|
||||
{
|
||||
int64 totalLength{};
|
||||
|
||||
for (size_t i = 0; i < rv.size(); ++i)
|
||||
totalLength += rv.getItem (i).range.getLength();
|
||||
|
||||
return totalLength;
|
||||
}
|
||||
|
||||
template <typename ValueType>
|
||||
static auto toString (const RangedValues<ValueType>& rv)
|
||||
{
|
||||
String s {};
|
||||
|
||||
for (size_t i = 0; i < rv.size(); ++i)
|
||||
{
|
||||
auto item = rv.getItem (i);
|
||||
s << item.range << ": " << item.value << "\n";
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
class RangesTestsBase : public UnitTest
|
||||
{
|
||||
public:
|
||||
using UnitTest::UnitTest;
|
||||
|
||||
void expectRange (Range<int64> actual, Range<int64> expected)
|
||||
{
|
||||
String failureMessage { "range " };
|
||||
failureMessage << actual << " did not equal expected range " << expected;
|
||||
expect (actual == expected, failureMessage);
|
||||
}
|
||||
};
|
||||
|
||||
class RangesTests : public RangesTestsBase
|
||||
{
|
||||
public:
|
||||
RangesTests() : RangesTestsBase ("Ranges", UnitTestCategories::containers) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
Ranges::Operations ops;
|
||||
|
||||
beginTest ("Ranges::set() - basics");
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ -3, 14 }, ops);
|
||||
expectRange (ranges.get (0), { -3, 14 });
|
||||
|
||||
ranges.set ({ 7, 20 }, ops);
|
||||
expectRange (ranges.get (0), { -3, 7 });
|
||||
expectRange (ranges.get (1), { 7, 20 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::set() - neighbouring ranges extents are modified");
|
||||
{
|
||||
Ranges ranges;
|
||||
ranges.set ({ -3, 14 }, ops);
|
||||
ranges.set ({ 19, 30 }, ops);
|
||||
ranges.set ({ 10, 25 }, ops);
|
||||
|
||||
// size_t doesn't always map to an existing overload for String::operator<< on all platforms
|
||||
expectEquals ((int64) ranges.size(), (int64) 3);
|
||||
expectRange (ranges.get (0), { -3, 10 });
|
||||
expectRange (ranges.get (1), { 10, 25 });
|
||||
expectRange (ranges.get (2), { 25, 30 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::set() - setting a range inside another one splits that range");
|
||||
{
|
||||
Ranges ranges;
|
||||
ranges.set ({ -3, 14 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 1);
|
||||
|
||||
//==============================================================================
|
||||
ranges.set ({ 3, 7 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 3);
|
||||
expectRange (ranges.get (0), { -3, 3 });
|
||||
expectRange (ranges.get (1), { 3, 7 });
|
||||
expectRange (ranges.get (2), { 7, 14 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::set() - old ranges falling within the bounds of a newly set are erased");
|
||||
{
|
||||
Ranges ranges;
|
||||
ranges.set ({ 0, 5 }, ops);
|
||||
ranges.set ({ 5, 10 }, ops);
|
||||
ranges.set ({ 15, 20 }, ops);
|
||||
ranges.set ({ 25, 30 }, ops);
|
||||
ranges.set ({ 35, 50 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 5);
|
||||
|
||||
//==============================================================================
|
||||
ranges.set ({ 4, 36 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 3);
|
||||
expectRange (ranges.get (0), { 0, 4 });
|
||||
expectRange (ranges.get (1), { 4, 36 });
|
||||
expectRange (ranges.get (2), { 36, 50 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::set() - setting an empty range should be a no-op");
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ops.clear();
|
||||
ranges.set ({ 0, 0 }, ops);
|
||||
expect (ranges.isEmpty());
|
||||
expect (ops.empty());
|
||||
}
|
||||
|
||||
beginTest ("Ranges::set() - setting a range inside another range");
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ 0, 48 }, ops);
|
||||
ranges.set ({ 48, 127 }, ops);
|
||||
ranges.set ({ 49, 94 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
expectRange (ranges.get (1), { 48, 49 });
|
||||
expectRange (ranges.get (2), { 49, 94 });
|
||||
expectRange (ranges.get (3), { 94, 127 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::split()");
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ 0, 48 }, ops);
|
||||
ranges.set ({ 48, 127 }, ops);
|
||||
|
||||
ops.clear();
|
||||
ranges.split (47, ops);
|
||||
|
||||
expectEquals ((int64) ops.size(), (int64) 1, "");
|
||||
const auto op0 = std::get_if<Ranges::Ops::Split> (&ops[0]);
|
||||
expect (op0 != nullptr);
|
||||
|
||||
if (op0 != nullptr)
|
||||
expectEquals ((int) op0->index, 0, "The 0th element should be split");
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 3, "");
|
||||
expectRange (ranges.get (0), { 0, 47 });
|
||||
expectRange (ranges.get (1), { 47, 48 });
|
||||
expectRange (ranges.get (2), { 48, 127 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::split() - splitting has no effect when no range begins before and ends after the location");
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ 0, 48 }, ops);
|
||||
ranges.set ({ 48, 127 }, ops);
|
||||
|
||||
ops.clear();
|
||||
ranges.split (48, ops);
|
||||
expectEquals ((int64) ops.size(), (int64) 0, "");
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 2, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
expectRange (ranges.get (1), { 48, 127 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::insert() - basics");
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.insert ({ -3, 14 }, ops);
|
||||
expectRange (ranges.get (0), { -3, 14 });
|
||||
|
||||
ranges.insert ({ 7, 20 }, ops);
|
||||
expectRange (ranges.get (0), { -3, 7 });
|
||||
expectRange (ranges.get (1), { 7, 20 });
|
||||
expectRange (ranges.get (2), { 20, 27 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::insert() - inserting shifts all following ranges");
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.insert ({ 10, 11 }, ops);
|
||||
ranges.insert ({ 0, 1 }, ops);
|
||||
|
||||
expectRange (ranges.get (0), { 0, 1 });
|
||||
expectRange (ranges.get (1), { 11, 12 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::insert() - inserting an empty range should be a no-op");
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ops.clear();
|
||||
ranges.insert ({ 0, 0 }, ops);
|
||||
expect (ranges.isEmpty());
|
||||
expect (ops.empty());
|
||||
}
|
||||
|
||||
const auto getTestRanges = [&ops]
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ 0, 48 }, ops);
|
||||
ranges.set ({ 48, 49 }, ops);
|
||||
ranges.set ({ 55, 94 }, ops);
|
||||
ranges.set ({ 94, 127 }, ops);
|
||||
|
||||
return ranges;
|
||||
};
|
||||
|
||||
beginTest ("Ranges::eraseFrom() - erasing beyond all ranges has no effect");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
ranges.eraseFrom (ranges.get (ranges.size() - 1).getEnd() + 5, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
expectRange (ranges.get (1), { 48, 49 });
|
||||
expectRange (ranges.get (2), { 55, 94 });
|
||||
expectRange (ranges.get (3), { 94, 127 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::eraseFrom() - erasing modifies the range that encloses the starting index");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
ranges.eraseFrom (122, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
expectRange (ranges.get (1), { 48, 49 });
|
||||
expectRange (ranges.get (2), { 55, 94 });
|
||||
expectRange (ranges.get (3), { 94, 122 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::eraseFrom() - ranges starting after the erased index are deleted entirely");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
ranges.eraseFrom (60, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 3, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
expectRange (ranges.get (1), { 48, 49 });
|
||||
expectRange (ranges.get (2), { 55, 60 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::eraseFrom() - erasing from a location outside any ranges will still drop subsequent ranges");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
ranges.eraseFrom (51, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 2, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
expectRange (ranges.get (1), { 48, 49 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::erase() - erasing a zero length range has no effect");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.erase ({ 30, 30 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
expectRange (ranges.get (1), { 48, 49 });
|
||||
expectRange (ranges.get (2), { 55, 94 });
|
||||
expectRange (ranges.get (3), { 94, 127 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::erase() - erasing inside a range splits that range");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.erase ({ 30, 31 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 5, "");
|
||||
expectRange (ranges.get (0), { 0, 30 });
|
||||
expectRange (ranges.get (1), { 31, 48 });
|
||||
expectRange (ranges.get (2), { 48, 49 });
|
||||
expectRange (ranges.get (3), { 55, 94 });
|
||||
expectRange (ranges.get (4), { 94, 127 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::erase() - erasing a range that completely overlaps existing ranges erases those");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.erase ({ 30, 70 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 3, "");
|
||||
expectRange (ranges.get (0), { 0, 30 });
|
||||
expectRange (ranges.get (1), { 70, 94 });
|
||||
expectRange (ranges.get (2), { 94, 127 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::erase() - erasing a range that no previous ranges covered has no effect");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.erase ({ 51, 53 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
expectRange (ranges.get (1), { 48, 49 });
|
||||
expectRange (ranges.get (2), { 55, 94 });
|
||||
expectRange (ranges.get (3), { 94, 127 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::erase() - erasing over a range beyond all limits clears all ranges");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.erase ({ -1000, 1000 }, ops);
|
||||
|
||||
expect (ranges.isEmpty());
|
||||
}
|
||||
|
||||
beginTest ("Ranges::drop() - dropping a range shifts all following ranges downward");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.drop ({ 48, 49 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 3, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
expectRange (ranges.get (1), { 54, 93 });
|
||||
expectRange (ranges.get (2), { 93, 126 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::drop() - dropping a range shifts all following ranges downward even if no range covered the dropped range previously");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.drop ({ 51, 53 }, ops);
|
||||
|
||||
expectEquals ((int64) ranges.size(), (int64) 4, "");
|
||||
expectRange (ranges.get (0), { 0, 48 });
|
||||
expectRange (ranges.get (1), { 48, 49 });
|
||||
expectRange (ranges.get (2), { 53, 92 });
|
||||
expectRange (ranges.get (3), { 92, 125 });
|
||||
}
|
||||
|
||||
beginTest ("Ranges::drop() - dropping a range covering all other ranges empties the collection");
|
||||
{
|
||||
auto ranges = getTestRanges();
|
||||
|
||||
ranges.drop ({ -1000, 1000 }, ops);
|
||||
expect (ranges.isEmpty());
|
||||
}
|
||||
|
||||
beginTest ("Ranges::covers()");
|
||||
{
|
||||
Ranges ranges;
|
||||
|
||||
ranges.set ({ 0, 48 }, ops);
|
||||
ranges.set ({ 48, 49 }, ops);
|
||||
ranges.set ({ 55, 94 }, ops);
|
||||
ranges.set ({ 94, 127 }, ops);
|
||||
ranges.set ({ 127, 150 }, ops);
|
||||
|
||||
expect (ranges.covers ({ 0, 48 }));
|
||||
expect (ranges.covers ({ 0, 20 }));
|
||||
expect (ranges.covers ({ 10, 30 }));
|
||||
expect (ranges.covers ({ 30, 48 }));
|
||||
expect (ranges.covers ({ 30, 49 }));
|
||||
expect (ranges.covers ({ 55, 150 }));
|
||||
expect (ranges.covers ({ 60, 145 }));
|
||||
|
||||
expect (! ranges.covers ({ -1, 10 }));
|
||||
expect (! ranges.covers ({ 1, 50 }));
|
||||
expect (! ranges.covers ({ 50, 140 }));
|
||||
expect (! ranges.covers ({ 149, 151 }));
|
||||
|
||||
expect (ranges.covers ({ 10, 10 }));
|
||||
expect (! ranges.covers ({ 151, 151 }));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class RangedValuesTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
RangedValuesTests() : UnitTest ("RangedValues", UnitTestCategories::containers) {}
|
||||
|
||||
template <typename ItemType>
|
||||
void expectRangedValuesItem (ItemType item, Range<int64> range, char value)
|
||||
{
|
||||
{
|
||||
String failureMessage { "range " };
|
||||
failureMessage << item.range << " did not equal expected range " << range;
|
||||
expect (item.range == range, failureMessage);
|
||||
}
|
||||
|
||||
{
|
||||
String failureMessage { "value '" };
|
||||
failureMessage << item.value << "' in range " << range << " did not equal expected value '" << value << "'";
|
||||
expect (item.value == value, failureMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
auto random = getRandom();
|
||||
Ranges::Operations ops;
|
||||
|
||||
const auto createRangedValuesObject = [&]
|
||||
{
|
||||
RangedValues<char> rangedValues;
|
||||
|
||||
rangedValues.set ({ 0, 10 }, 'a', ops);
|
||||
rangedValues.set ({ 11, 20 }, 'b', ops);
|
||||
rangedValues.set ({ 23, 30 }, 'c', ops);
|
||||
|
||||
return rangedValues;
|
||||
};
|
||||
|
||||
beginTest ("RangedValues::set() with distinct value overlapping other ranges");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
rangedValues.set ({ 5, 15 }, 'd', ops);
|
||||
|
||||
expect (! rangedValues.isEmpty());
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 5, 15 }, 'd');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 15, 20 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c');
|
||||
|
||||
rangedValues.set ({ 19, 24 }, 'e', ops);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 15, 19 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 19, 24 }, 'e');
|
||||
expectRangedValuesItem (rangedValues.getItem (4), { 24, 30 }, 'c');
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::set() with distinct value in corner cases");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
rangedValues.set ({ -1, 0 }, 'd', ops);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { -1, 0 }, 'd');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 0, 10 }, 'a');
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::set() with same value with merging disallowed");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
rangedValues.set ({ 5, 15 }, 'b', ops, MergeEqualItemsNo{});
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 5, 15 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 15, 20 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c');
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::set() with same value with merging allowed");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
rangedValues.set ({ 5, 15 }, 'b', ops, MergeEqualItemsYes{});
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 5 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 5, 20 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 23, 30 }, 'c');
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::set() - setting an empty Range should be a no-op");
|
||||
{
|
||||
RangedValues<char> rangedValues;
|
||||
|
||||
ops.clear();
|
||||
rangedValues.set ({ 0, 0 }, 'a', ops);
|
||||
expect (rangedValues.isEmpty());
|
||||
expect (ops.empty());
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::set() - setting a range inside another range");
|
||||
{
|
||||
RangedValues<char> rangedValues;
|
||||
|
||||
rangedValues.set ({ 0, 48 }, 'a', ops);
|
||||
rangedValues.set ({ 48, 127 }, 'b', ops);
|
||||
rangedValues.set ({ 49, 94 }, 'c', ops);
|
||||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 4, "");
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 48 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 48, 49 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 49, 94 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 94, 127 }, 'b');
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::getIntersectionsWith()");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
{
|
||||
const auto intersections = rangedValues.getIntersectionsWith ({ 5, 43 });
|
||||
|
||||
expectRangedValuesItem (intersections.getItem (0), { 5, 10 }, 'a');
|
||||
expectRangedValuesItem (intersections.getItem (1), { 11, 20 }, 'b');
|
||||
expectRangedValuesItem (intersections.getItem (2), { 23, 30 }, 'c');
|
||||
}
|
||||
|
||||
{
|
||||
const auto intersections = rangedValues.getIntersectionsWith ({ -10, 3 });
|
||||
|
||||
expectRangedValuesItem (intersections.getItem (0), { 0, 3 }, 'a');
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::insert() fuzzing - insert always increases the total covered range");
|
||||
{
|
||||
for (auto i = 0; i != 100; ++i)
|
||||
{
|
||||
auto rangedValuesNotMerged = createRangedValuesObject();
|
||||
auto rangedValuesMerged = createRangedValuesObject();
|
||||
|
||||
const auto totalLengthBeforeInsert = getCumulativeRangeLengths (rangedValuesNotMerged);
|
||||
|
||||
const auto beginInsertionAt = (int64) random.nextInt (100) - 50;
|
||||
const auto numElemsToInsert = (int64) random.nextInt (1000);
|
||||
|
||||
rangedValuesNotMerged.insert ({ Range<int64>::withStartAndLength (beginInsertionAt, numElemsToInsert) },
|
||||
'a' + (char) random.nextInt (25),
|
||||
ops,
|
||||
MergeEqualItemsNo{});
|
||||
|
||||
expectEquals (getCumulativeRangeLengths (rangedValuesNotMerged) - totalLengthBeforeInsert, numElemsToInsert);
|
||||
|
||||
rangedValuesMerged.insert ({ Range<int64>::withStartAndLength (beginInsertionAt, numElemsToInsert) },
|
||||
'a' + (char) random.nextInt (25),
|
||||
ops,
|
||||
MergeEqualItemsYes{});
|
||||
|
||||
expectEquals (getCumulativeRangeLengths (rangedValuesMerged) - totalLengthBeforeInsert, numElemsToInsert);
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::insert() with distinct value inside another range");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 3);
|
||||
|
||||
rangedValues.insert ({ 2, 4 }, 'd', ops);
|
||||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 5);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 2 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 2, 4 }, 'd');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 4, 12 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 13, 22 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (4), { 25, 32 }, 'c');
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::insert() with same value inside another range");
|
||||
{
|
||||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 3);
|
||||
|
||||
rangedValues.insert ({ 2, 4 }, 'a', ops, MergeEqualItemsYes{});
|
||||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 3);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 12 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 13, 22 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 25, 32 }, 'c');
|
||||
}
|
||||
{
|
||||
auto rangedValues = createRangedValuesObject();
|
||||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 3);
|
||||
|
||||
rangedValues.insert ({ 2, 4 }, 'a', ops, MergeEqualItemsNo{});
|
||||
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 5);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 2 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 2, 4 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 4, 12 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 13, 22 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (4), { 25, 32 }, 'c');
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::insert() - inserting an empty Range should be a no-op");
|
||||
{
|
||||
{
|
||||
RangedValues<char> emptyRangedValues;
|
||||
|
||||
ops.clear();
|
||||
emptyRangedValues.insert ({ 0, 0 }, 'a', ops);
|
||||
expect (emptyRangedValues.isEmpty());
|
||||
expect (ops.empty());
|
||||
}
|
||||
|
||||
{
|
||||
RangedValues<char> rangedValues;
|
||||
rangedValues.set ({ 0, 10 }, 'a', ops);
|
||||
|
||||
ops.clear();
|
||||
rangedValues.insert ({ 0, 0 }, 'a', ops);
|
||||
expect (ops.empty());
|
||||
expectEquals ((int64) rangedValues.size(), (int64) 1);
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
}
|
||||
}
|
||||
|
||||
const auto createRangedValuesObjectForErase = [&]
|
||||
{
|
||||
RangedValues<char> rangedValues;
|
||||
|
||||
rangedValues.set ({ 0, 10 }, 'a', ops);
|
||||
rangedValues.set ({ 11, 20 }, 'b', ops);
|
||||
rangedValues.set ({ 23, 30 }, 'c', ops);
|
||||
rangedValues.set ({ 35, 45 }, 'c', ops);
|
||||
rangedValues.set ({ 45, 60 }, 'd', ops);
|
||||
|
||||
return rangedValues;
|
||||
};
|
||||
|
||||
beginTest ("RangedValues::erase() - erase will not shift subsequent ranges downward");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
|
||||
rangedValues.erase ({ 15, 16 }, ops);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 11, 15 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 16, 20 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 23, 30 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (4), { 35, 45 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (5), { 45, 60 }, 'd');
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::eraseUpTo() - erasing before all ranges has no effect");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
|
||||
rangedValues.eraseUpTo (rangedValues.getRanges().get (0).getStart(), ops);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 11, 20 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 23, 30 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 35, 45 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (4), { 45, 60 }, 'd');
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::eraseUpTo() - erasing values up to, not including 15");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
|
||||
rangedValues.eraseUpTo (15, ops);
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 15, 20 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 23, 30 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 35, 45 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 45, 60 }, 'd');
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::eraseUpTo() - erasing up to the end of all ranges clears the container");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
const auto ranges = rangedValues.getRanges();
|
||||
|
||||
rangedValues.eraseUpTo (ranges.get (ranges.size() - 1).getEnd(), ops);
|
||||
|
||||
expect (rangedValues.isEmpty());
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::drop() - drop shifts ranges downward on the right side");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
|
||||
rangedValues.drop ({ 15, 16 }, ops, MergeEqualItemsNo{});
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 11, 15 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 15, 19 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 22, 29 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (4), { 34, 44 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (5), { 44, 59 }, 'd');
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::drop() - drop can be used to merge equal values");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
|
||||
rangedValues.drop ({ 15, 16 }, ops, MergeEqualItemsYes{});
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 11, 19 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 22, 29 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 34, 44 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (4), { 44, 59 }, 'd');
|
||||
}
|
||||
|
||||
beginTest ("RangedValues::drop() - the merge happens at the seam caused by the drop and does not extend beyond");
|
||||
{
|
||||
auto rangedValues = createRangedValuesObjectForErase();
|
||||
rangedValues.set ({ 20, 30 }, 'b', ops, MergeEqualItemsNo{});
|
||||
|
||||
rangedValues.drop ({ 15, 16 }, ops, MergeEqualItemsYes{});
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (0), { 0, 10 }, 'a');
|
||||
|
||||
// These two items are not merged, even though they form a contiguous range, because
|
||||
// they were disjoint before the drop and they don't touch each other at the drop
|
||||
// seam of 15.
|
||||
expectRangedValuesItem (rangedValues.getItem (1), { 11, 19 }, 'b');
|
||||
expectRangedValuesItem (rangedValues.getItem (2), { 19, 29 }, 'b');
|
||||
|
||||
expectRangedValuesItem (rangedValues.getItem (3), { 34, 44 }, 'c');
|
||||
expectRangedValuesItem (rangedValues.getItem (4), { 44, 59 }, 'd');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class IntersectingRangedValuesTests : public RangesTestsBase
|
||||
{
|
||||
public:
|
||||
IntersectingRangedValuesTests() : RangesTestsBase ("IntersectingRangedValues", UnitTestCategories::containers) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
Ranges::Operations ops;
|
||||
|
||||
beginTest ("IntersectingRangedValuesTests - iterating over multiple RangedValues");
|
||||
{
|
||||
RangedValues<int> rv1;
|
||||
rv1.set ({ 3, 8}, 1, ops);
|
||||
rv1.set ({ 9, 16}, 2, ops);
|
||||
rv1.set ({ 30, 40}, 3, ops);
|
||||
|
||||
RangedValues<int> rv2;
|
||||
rv2.set ({ 0, 4}, 7, ops);
|
||||
rv2.set ({ 4, 6}, 11, ops);
|
||||
rv2.set ({ 6, 25}, 13, ops);
|
||||
rv2.set ({ 27, 55}, 17, ops);
|
||||
|
||||
RangedValues<int> rv3;
|
||||
rv3.set ({ -2, 10}, -1, ops);
|
||||
rv3.set ({ 15, 19}, -2, ops);
|
||||
rv3.set ({ 22, 36}, -3, ops);
|
||||
|
||||
int iteration = 0;
|
||||
|
||||
for (const auto [range, v1, v2, v3] : makeIntersectingRangedValues (&rv1, &rv2, &rv3))
|
||||
{
|
||||
if (iteration == 0)
|
||||
{
|
||||
expectRange (range, Range<int64> { 3, 4 });
|
||||
expect (v1 == 1 && v2 == 7 && v3 == -1);
|
||||
}
|
||||
|
||||
if (iteration == 1)
|
||||
{
|
||||
expectRange (range, Range<int64> { 4, 6 });
|
||||
expect (v1 == 1 && v2 == 11 && v3 == -1);
|
||||
}
|
||||
|
||||
if (iteration == 2)
|
||||
{
|
||||
expectRange (range, Range<int64> { 6, 8 });
|
||||
expect (v1 == 1 && v2 == 13 && v3 == -1);
|
||||
}
|
||||
|
||||
if (iteration == 3)
|
||||
{
|
||||
expectRange (range, Range<int64> { 9, 10 });
|
||||
expect (v1 == 2 && v2 == 13 && v3 == -1);
|
||||
}
|
||||
|
||||
if (iteration == 4)
|
||||
{
|
||||
expectRange (range, Range<int64> { 15, 16 });
|
||||
expect (v1 == 2 && v2 == 13 && v3 == -2);
|
||||
}
|
||||
|
||||
if (iteration == 5)
|
||||
{
|
||||
expectRange (range, Range<int64> { 30, 36 });
|
||||
expect (v1 == 3 && v2 == 17 && v3 == -3);
|
||||
}
|
||||
|
||||
++iteration;
|
||||
}
|
||||
|
||||
expectEquals (iteration, 6);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static RangesTests rangesTests;
|
||||
static RangedValuesTests rangedValuesTests;
|
||||
static IntersectingRangedValuesTests intersectingRangedValuesTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce::detail
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
class ShapedText::Impl
|
||||
{
|
||||
public:
|
||||
Impl (String textIn, Options optionsIn)
|
||||
: options { std::move (optionsIn) },
|
||||
text { std::move (textIn) }
|
||||
{
|
||||
}
|
||||
|
||||
void draw (const Graphics& g, AffineTransform transform) const
|
||||
{
|
||||
drawJustifiedText (justifiedText, g, transform);
|
||||
}
|
||||
|
||||
float getHeight() const
|
||||
{
|
||||
return justifiedText.getHeight();
|
||||
}
|
||||
|
||||
int64 getNumGlyphs() const
|
||||
{
|
||||
return simpleShapedText.getNumGlyphs();
|
||||
}
|
||||
|
||||
const detail::RangedValues<LineMetrics>& getLineMetricsForGlyphRange() const
|
||||
{
|
||||
return justifiedText.getLineMetricsForGlyphRange();
|
||||
}
|
||||
|
||||
const detail::Ranges& getLineTextRanges() const
|
||||
{
|
||||
return simpleShapedText.getLineTextRanges();
|
||||
}
|
||||
|
||||
auto& getText() const
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
auto getTextRange (int64 glyphIndex) const
|
||||
{
|
||||
return simpleShapedText.getTextRange (glyphIndex);
|
||||
}
|
||||
|
||||
auto isLtr (int64 glyphIndex) const
|
||||
{
|
||||
return simpleShapedText.isLtr (glyphIndex);
|
||||
}
|
||||
|
||||
int64 getTextIndexForCaret (Point<float> p) const
|
||||
{
|
||||
const auto getGlyph = [&] (int64 i)
|
||||
{
|
||||
return simpleShapedText.getGlyphs()[(size_t) i];
|
||||
};
|
||||
|
||||
if (getNumGlyphs() == 0)
|
||||
return 0;
|
||||
|
||||
const auto glyphOnTheRight = justifiedText.getGlyphIndexToTheRightOf (p);
|
||||
|
||||
if (glyphOnTheRight >= getNumGlyphs())
|
||||
{
|
||||
const auto glyphOnTheLeft = glyphOnTheRight - 1;
|
||||
const auto ltr = simpleShapedText.getGlyphLookup().find (getGlyph (glyphOnTheLeft).cluster)->value.ltr;
|
||||
|
||||
if (ltr)
|
||||
return simpleShapedText.getTextIndexAfterGlyph (glyphOnTheLeft);
|
||||
|
||||
return simpleShapedText.getGlyphs()[(size_t) glyphOnTheLeft].cluster;
|
||||
}
|
||||
|
||||
const auto ltr = simpleShapedText.getGlyphLookup().find (getGlyph (glyphOnTheRight).cluster)->value.ltr;
|
||||
|
||||
if (ltr)
|
||||
return simpleShapedText.getGlyphs()[(size_t) glyphOnTheRight].cluster;
|
||||
|
||||
return simpleShapedText.getTextIndexAfterGlyph (glyphOnTheRight);
|
||||
}
|
||||
|
||||
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const
|
||||
{
|
||||
simpleShapedText.getGlyphRanges (textRange, outRanges);
|
||||
}
|
||||
|
||||
RectangleList<float> getGlyphsBounds (Range<int64> glyphRange) const
|
||||
{
|
||||
return justifiedText.getGlyphsBounds (glyphRange);
|
||||
}
|
||||
|
||||
auto getGlyphAnchor (int64 index) const
|
||||
{
|
||||
return justifiedText.getGlyphAnchor (index);
|
||||
}
|
||||
|
||||
Span<const float> getMinimumRequiredWidthForLines() const
|
||||
{
|
||||
return justifiedText.getMinimumRequiredWidthForLines();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
auto& getSimpleShapedText() const { return simpleShapedText; }
|
||||
|
||||
auto& getJustifiedText() const { return justifiedText; }
|
||||
|
||||
private:
|
||||
ShapedTextOptions options;
|
||||
String text;
|
||||
SimpleShapedText simpleShapedText { &text, options };
|
||||
JustifiedText justifiedText { &simpleShapedText, options };
|
||||
};
|
||||
|
||||
ShapedText::ShapedText() : ShapedText ("", {})
|
||||
{
|
||||
}
|
||||
|
||||
ShapedText::ShapedText (String text) : ShapedText (std::move (text), {})
|
||||
{
|
||||
}
|
||||
|
||||
ShapedText::ShapedText (String text, Options options)
|
||||
{
|
||||
impl = std::make_shared<Impl> (std::move (text), std::move (options));
|
||||
}
|
||||
|
||||
void ShapedText::draw (const Graphics& g, AffineTransform transform) const
|
||||
{
|
||||
impl->draw (g, transform);
|
||||
}
|
||||
|
||||
float ShapedText::getHeight() const
|
||||
{
|
||||
return impl->getHeight();
|
||||
}
|
||||
|
||||
int64 ShapedText::getNumGlyphs() const
|
||||
{
|
||||
return impl->getNumGlyphs();
|
||||
}
|
||||
|
||||
const detail::RangedValues<LineMetrics>& ShapedText::getLineMetricsForGlyphRange() const
|
||||
{
|
||||
return impl->getLineMetricsForGlyphRange();
|
||||
}
|
||||
|
||||
const detail::Ranges& ShapedText::getLineTextRanges() const
|
||||
{
|
||||
return impl->getLineTextRanges();
|
||||
}
|
||||
|
||||
const String& ShapedText::getText() const
|
||||
{
|
||||
return impl->getText();
|
||||
}
|
||||
|
||||
Range<int64> ShapedText::getTextRange (int64 glyphIndex) const
|
||||
{
|
||||
return impl->getTextRange (glyphIndex);
|
||||
}
|
||||
|
||||
bool ShapedText::isLtr (int64 glyphIndex) const
|
||||
{
|
||||
return impl->isLtr (glyphIndex);
|
||||
}
|
||||
|
||||
int64 ShapedText::getTextIndexForCaret (Point<float> p) const
|
||||
{
|
||||
return impl->getTextIndexForCaret (p);
|
||||
}
|
||||
|
||||
void ShapedText::getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const
|
||||
{
|
||||
return impl->getGlyphRanges (textRange, outRanges);
|
||||
}
|
||||
|
||||
RectangleList<float> ShapedText::getGlyphsBounds (Range<int64> glyphRange) const
|
||||
{
|
||||
return impl->getGlyphsBounds (glyphRange);
|
||||
}
|
||||
|
||||
GlyphAnchorResult ShapedText::getGlyphAnchor (int64 index) const
|
||||
{
|
||||
return impl->getGlyphAnchor (index);
|
||||
}
|
||||
|
||||
Span<const float> ShapedText::getMinimumRequiredWidthForLines() const
|
||||
{
|
||||
return impl->getMinimumRequiredWidthForLines();
|
||||
}
|
||||
|
||||
const JustifiedText& ShapedText::getJustifiedText() const { return impl->getJustifiedText(); }
|
||||
|
||||
const SimpleShapedText& ShapedText::getSimpleShapedText() const { return impl->getSimpleShapedText(); }
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
/* Class that can visually shape a Unicode string provided a list of Fonts corresponding to
|
||||
sub-ranges of the string.
|
||||
*/
|
||||
class JUCE_API ShapedText
|
||||
{
|
||||
public:
|
||||
using Options = ShapedTextOptions;
|
||||
|
||||
ShapedText();
|
||||
|
||||
explicit ShapedText (String text);
|
||||
|
||||
ShapedText (String text, Options options);
|
||||
|
||||
/* Returns the text which was used to construct this object. */
|
||||
const String& getText() const;
|
||||
|
||||
Span<const ShapedGlyph> getGlyphs() const;
|
||||
|
||||
/* Returns the text's codepoint range, to which the glyph under the provided index belongs.
|
||||
|
||||
This range will have a length of at least one, and potentially more than one if ligatures
|
||||
are enabled.
|
||||
*/
|
||||
Range<int64> getTextRange (int64 glyphIndex) const;
|
||||
|
||||
bool isLtr (int64 glyphIndex) const;
|
||||
|
||||
int64 getTextIndexForCaret (Point<float> p) const;
|
||||
|
||||
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const;
|
||||
|
||||
RectangleList<float> getGlyphsBounds (Range<int64> glyphRange) const;
|
||||
|
||||
/* @see JustifiedText::getGlyphAnchor() */
|
||||
GlyphAnchorResult getGlyphAnchor (int64 index) const;
|
||||
|
||||
/* Returns the widths for each line, that the glyphs would require to be rendered without being
|
||||
truncated. This will or will not include the space required by trailing whitespaces in the
|
||||
line based on the ShapedTextOptions::withTrailingWhitespacesShouldFit() value.
|
||||
|
||||
This value isn't affected by the Justification parameter, it just reports the amount of
|
||||
width that would be required to avoid truncation.
|
||||
*/
|
||||
Span<const float> getMinimumRequiredWidthForLines() const;
|
||||
|
||||
/* @see JustifiedText::accessTogetherWith */
|
||||
template <typename Callable, typename... RangedValues>
|
||||
void accessTogetherWith (Callable&& callback, RangedValues&&... rangedValues) const
|
||||
{
|
||||
getJustifiedText().accessTogetherWith (std::forward<Callable> (callback),
|
||||
std::forward<RangedValues> (rangedValues)...);
|
||||
}
|
||||
|
||||
/* Draws the text. */
|
||||
void draw (const Graphics& g, AffineTransform transform) const;
|
||||
|
||||
/* @see JustifiedText::getHeight
|
||||
*/
|
||||
float getHeight() const;
|
||||
|
||||
int64 getNumGlyphs() const;
|
||||
|
||||
const detail::RangedValues<LineMetrics>& getLineMetricsForGlyphRange() const;
|
||||
|
||||
const detail::Ranges& getLineTextRanges() const;
|
||||
|
||||
/* @internal */
|
||||
const JustifiedText& getJustifiedText() const;
|
||||
|
||||
/* @internal */
|
||||
const SimpleShapedText& getSimpleShapedText() const;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
/** Types of text direction. This may also be applied to characters. */
|
||||
enum class TextDirection
|
||||
{
|
||||
ltr, // This text reads left to right.
|
||||
rtl // This text reads right to left.
|
||||
};
|
||||
|
||||
class ShapedTextOptions
|
||||
{
|
||||
private:
|
||||
auto tie() const
|
||||
{
|
||||
return std::tie (justification,
|
||||
readingDir,
|
||||
maxWidth,
|
||||
alignmentWidth,
|
||||
height,
|
||||
fontsForRange,
|
||||
language,
|
||||
firstLineIndent,
|
||||
leading,
|
||||
additiveLineSpacing,
|
||||
baselineAtZero,
|
||||
allowBreakingInsideWord,
|
||||
trailingWhitespacesShouldFit,
|
||||
maxNumLines,
|
||||
ellipsis);
|
||||
}
|
||||
|
||||
public:
|
||||
bool operator== (const ShapedTextOptions& other) const { return tie() == other.tie(); }
|
||||
bool operator!= (const ShapedTextOptions& other) const { return tie() != other.tie(); }
|
||||
|
||||
//==============================================================================
|
||||
[[nodiscard]] ShapedTextOptions withJustification (Justification x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::justification, x);
|
||||
}
|
||||
|
||||
/* This option will use soft wrapping for lines that are longer than the specified value,
|
||||
and it will also align each line to this width, using the Justification provided in
|
||||
withJustification.
|
||||
|
||||
The alignment width can be overriden using withAlignmentWidth, but currently we only need
|
||||
to do this for the TextEditor.
|
||||
*/
|
||||
[[nodiscard]] ShapedTextOptions withMaxWidth (float x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::maxWidth, x);
|
||||
}
|
||||
|
||||
/* With this option each line will be aligned only if it's shorter or equal to the alignment
|
||||
width. Otherwise, the line's x anchor will be 0.0f. This is in contrast to using
|
||||
withMaxWidth only, which will modify the x anchor of RTL lines that are too long, to ensure
|
||||
that it's the logical end of the text that falls outside the visible bounds.
|
||||
|
||||
The alignment width is also a distinct value from the value used for soft wrapping which is
|
||||
specified using withMaxWidth.
|
||||
|
||||
This option is specifically meant to support an existing TextEditor behaviour, where text
|
||||
can be aligned even when word wrapping is off. You probably don't need to use this function,
|
||||
unless you want to reproduce the particular behaviour seen in the TextEditor, and should
|
||||
only use withMaxWidth, if alignment is required.
|
||||
|
||||
With this option off, text is either not aligned, or aligned to the width specified using
|
||||
withMaxWidth.
|
||||
|
||||
When this option is in use, it overrides the width specified in withMaxWidth for alignment
|
||||
purposes, but not for line wrapping purposes.
|
||||
|
||||
It also accommodates the fact that the TextEditor has a scrolling feature and text never
|
||||
becomes unreachable, even if the lines are longer than the viewport's width.
|
||||
*/
|
||||
[[nodiscard]] ShapedTextOptions withAlignmentWidth (float x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::alignmentWidth, x);
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withHeight (float x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::height, x);
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withFont (Font x) const
|
||||
{
|
||||
RangedValues<Font> fonts;
|
||||
detail::Ranges::Operations ops;
|
||||
fonts.set ({ 0, std::numeric_limits<int64>::max() }, x, ops);
|
||||
|
||||
return withMember (*this, &ShapedTextOptions::fontsForRange, std::move (fonts));
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withFonts (const detail::RangedValues<Font>& x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::fontsForRange, x);
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withLanguage (StringRef x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::language, x);
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withFirstLineIndent (float x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::firstLineIndent, x);
|
||||
}
|
||||
|
||||
/* This controls the space between lines using a proportional value, with a default of 1.0,
|
||||
meaning single line spacing i.e. the descender of the current line + ascender of the next
|
||||
line. This value is multiplied by the leading provided here.
|
||||
*/
|
||||
[[nodiscard]] ShapedTextOptions withLeading (float x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::leading, x);
|
||||
}
|
||||
|
||||
/* This controls the space between lines using an additive absolute value, with a default of 0.0.
|
||||
This value is added to the spacing between each two lines.
|
||||
*/
|
||||
[[nodiscard]] ShapedTextOptions withAdditiveLineSpacing (float x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::additiveLineSpacing, x);
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withBaselineAtZero (bool x = true) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::baselineAtZero, x);
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withTrailingWhitespacesShouldFit (bool x = true) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::trailingWhitespacesShouldFit, x);
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withMaxNumLines (int64 x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::maxNumLines, x);
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withEllipsis (String x = String::charToString ((juce_wchar) 0x2026)) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::ellipsis, std::move (x));
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withReadingDirection (std::optional<TextDirection> x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::readingDir, x);
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withAllowBreakingInsideWord (bool x = true) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::allowBreakingInsideWord, x);
|
||||
}
|
||||
|
||||
const auto& getReadingDirection() const { return readingDir; }
|
||||
const auto& getJustification() const { return justification; }
|
||||
const auto& getMaxWidth() const { return maxWidth; }
|
||||
const auto& getAlignmentWidth() const { return alignmentWidth; }
|
||||
const auto& getHeight() const { return height; }
|
||||
const auto& getFontsForRange() const { return fontsForRange; }
|
||||
const auto& getLanguage() const { return language; }
|
||||
const auto& getFirstLineIndent() const { return firstLineIndent; }
|
||||
const auto& getLeading() const { return leading; }
|
||||
const auto& getAdditiveLineSpacing() const { return additiveLineSpacing; }
|
||||
const auto& isBaselineAtZero() const { return baselineAtZero; }
|
||||
const auto& getTrailingWhitespacesShouldFit() const { return trailingWhitespacesShouldFit; }
|
||||
const auto& getMaxNumLines() const { return maxNumLines; }
|
||||
const auto& getEllipsis() const { return ellipsis; }
|
||||
const auto& getAllowBreakingInsideWord() const { return allowBreakingInsideWord; }
|
||||
|
||||
private:
|
||||
Justification justification { Justification::topLeft };
|
||||
std::optional<TextDirection> readingDir;
|
||||
std::optional<float> maxWidth;
|
||||
std::optional<float> alignmentWidth;
|
||||
std::optional<float> height;
|
||||
|
||||
detail::RangedValues<Font> fontsForRange = std::invoke ([&]
|
||||
{
|
||||
detail::RangedValues<Font> result;
|
||||
detail::Ranges::Operations ops;
|
||||
result.set ({ 0, std::numeric_limits<int64>::max() }, FontOptions { 15.0f }, ops);
|
||||
return result;
|
||||
});
|
||||
|
||||
String language = SystemStats::getDisplayLanguage();
|
||||
float firstLineIndent = 0.0f;
|
||||
float leading = 1.0f;
|
||||
float additiveLineSpacing = 0.0f;
|
||||
bool baselineAtZero = false;
|
||||
bool allowBreakingInsideWord = false;
|
||||
bool trailingWhitespacesShouldFit = true;
|
||||
int64 maxNumLines = std::numeric_limits<int64>::max();
|
||||
String ellipsis;
|
||||
};
|
||||
|
||||
struct ShapedGlyph
|
||||
{
|
||||
ShapedGlyph (uint32_t glyphIdIn,
|
||||
int64 clusterIn,
|
||||
bool unsafeToBreakIn,
|
||||
bool whitespaceIn,
|
||||
bool newlineIn,
|
||||
int8_t distanceFromLigatureIn,
|
||||
Point<float> advanceIn,
|
||||
Point<float> offsetIn)
|
||||
: advance (advanceIn),
|
||||
offset (offsetIn),
|
||||
cluster (clusterIn),
|
||||
glyphId (glyphIdIn),
|
||||
unsafeToBreak (unsafeToBreakIn),
|
||||
whitespace (whitespaceIn),
|
||||
newline (newlineIn),
|
||||
distanceFromLigature (distanceFromLigatureIn) {}
|
||||
|
||||
bool isUnsafeToBreak() const { return unsafeToBreak; }
|
||||
bool isWhitespace() const { return whitespace; }
|
||||
bool isNewline() const { return newline; }
|
||||
|
||||
bool isNonLigature() const { return distanceFromLigature == 0; }
|
||||
bool isLigature() const { return distanceFromLigature < 0; }
|
||||
bool isPlaceholderForLigature() const { return distanceFromLigature > 0; }
|
||||
|
||||
int8_t getDistanceFromLigature() const { return distanceFromLigature; }
|
||||
int8_t getNumTrailingLigaturePlaceholders() const { return -distanceFromLigature; }
|
||||
|
||||
Point<float> advance;
|
||||
Point<float> offset;
|
||||
int64 cluster;
|
||||
uint32_t glyphId;
|
||||
|
||||
private:
|
||||
// These are effectively bools, pack into a single int once we have more than four flags.
|
||||
int8_t unsafeToBreak;
|
||||
int8_t whitespace;
|
||||
int8_t newline;
|
||||
int8_t distanceFromLigature;
|
||||
};
|
||||
|
||||
struct GlyphLookupEntry
|
||||
{
|
||||
Range<int64> glyphRange;
|
||||
bool ltr = true;
|
||||
};
|
||||
|
||||
class SimpleShapedText
|
||||
{
|
||||
public:
|
||||
/* Shapes and lays out the first contiguous sequence of ranges specified in the fonts
|
||||
parameter.
|
||||
*/
|
||||
SimpleShapedText (const String* data,
|
||||
const ShapedTextOptions& options);
|
||||
|
||||
const auto& getLineNumbersForGlyphRanges() const { return lineNumbersForGlyphRanges; }
|
||||
|
||||
const auto& getLineTextRanges() const { return lineTextRanges; }
|
||||
|
||||
const auto& getResolvedFonts() const { return resolvedFonts; }
|
||||
|
||||
Range<int64> getTextRange (int64 glyphIndex) const;
|
||||
|
||||
/* Returns true if the specified glyph is inside to an LTR run.
|
||||
*/
|
||||
bool isLtr (int64 glyphIndex) const;
|
||||
|
||||
/* This function may fail to return an out range, even if the provided textRange falls inside
|
||||
the string range used for the creation of the ShapedText object.
|
||||
|
||||
This is because the shaping process could fail due to insufficient glyph resolution to the
|
||||
point, where it will produce zero glyphs for the provided string.
|
||||
*/
|
||||
void getGlyphRanges (Range<int64> textRange, std::vector<Range<int64>>& outRanges) const;
|
||||
|
||||
/* Returns the input codepoint index that follows the glyph in a logical sense. So for LTR text
|
||||
this is the cluster number of the glyph to the right. For RTL text it's the one on the left.
|
||||
|
||||
If there is no subsequent glyph, the returned number is the first Unicode codepoint index
|
||||
that isn't covered by the cluster to which the selected glyph belongs, so for the glyph 'o'
|
||||
in "hello" this would be 5, given there are no ligatures in use.
|
||||
*/
|
||||
int64 getTextIndexAfterGlyph (int64 glyphIndex) const;
|
||||
|
||||
int64 getNumLines() const { return (int64) lineNumbersForGlyphRanges.getRanges().size(); }
|
||||
int64 getNumGlyphs() const { return (int64) glyphsInVisualOrder.size(); }
|
||||
|
||||
juce_wchar getCodepoint (int64 glyphIndex) const;
|
||||
|
||||
Span<const ShapedGlyph> getGlyphs (Range<int64> glyphRange) const;
|
||||
|
||||
Span<const ShapedGlyph> getGlyphs() const;
|
||||
|
||||
const auto& getGlyphLookup() const { return glyphLookup; }
|
||||
|
||||
private:
|
||||
void shape (const String& data,
|
||||
const ShapedTextOptions& options);
|
||||
|
||||
const String& string;
|
||||
std::vector<ShapedGlyph> glyphsInVisualOrder;
|
||||
detail::RangedValues<int64> lineNumbersForGlyphRanges;
|
||||
detail::Ranges lineTextRanges;
|
||||
detail::RangedValues<Font> resolvedFonts;
|
||||
detail::RangedValues<GlyphLookupEntry> glyphLookup;
|
||||
|
||||
JUCE_LEAK_DETECTOR (SimpleShapedText)
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
struct UnicodeHelpers
|
||||
{
|
||||
UnicodeHelpers() = delete;
|
||||
|
||||
static std::vector<int> getLineBreaks (const String& data);
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
DropShadow::DropShadow (Colour shadowColour, const int r, Point<int> o) noexcept
|
||||
: colour (shadowColour), radius (r), offset (o)
|
||||
{
|
||||
jassert (radius > 0);
|
||||
}
|
||||
|
||||
void DropShadow::drawForImage (Graphics& g, const Image& srcImage) const
|
||||
{
|
||||
jassert (radius > 0);
|
||||
|
||||
if (! srcImage.isValid())
|
||||
return;
|
||||
|
||||
auto blurred = srcImage.convertedToFormat (Image::SingleChannel);
|
||||
blurred.setBackupEnabled (false);
|
||||
|
||||
blurred.getPixelData()->applySingleChannelBoxBlurEffect (radius);
|
||||
|
||||
g.setColour (colour);
|
||||
g.drawImageAt (blurred, offset.x, offset.y, true);
|
||||
}
|
||||
|
||||
void DropShadow::drawForPath (Graphics& g, const Path& path) const
|
||||
{
|
||||
jassert (radius > 0);
|
||||
|
||||
auto area = (path.getBounds().getSmallestIntegerContainer() + offset)
|
||||
.expanded (radius + 1)
|
||||
.getIntersection (g.getClipBounds().expanded (radius + 1));
|
||||
|
||||
if (area.getWidth() <= 2 || area.getHeight() <= 2)
|
||||
return;
|
||||
|
||||
Image pathImage { Image::SingleChannel, area.getWidth(), area.getHeight(), true };
|
||||
pathImage.setBackupEnabled (false);
|
||||
|
||||
{
|
||||
Graphics g2 (pathImage);
|
||||
g2.setColour (Colours::white);
|
||||
g2.fillPath (path, AffineTransform::translation ((float) (offset.x - area.getX()),
|
||||
(float) (offset.y - area.getY())));
|
||||
}
|
||||
|
||||
pathImage.getPixelData()->applySingleChannelBoxBlurEffect (radius);
|
||||
|
||||
g.setColour (colour);
|
||||
g.drawImageAt (pathImage, area.getX(), area.getY(), true);
|
||||
}
|
||||
|
||||
static void drawShadowSection (Graphics& g, ColourGradient& cg, Rectangle<float> area,
|
||||
bool isCorner, float centreX, float centreY, float edgeX, float edgeY)
|
||||
{
|
||||
cg.point1 = area.getRelativePoint (centreX, centreY);
|
||||
cg.point2 = area.getRelativePoint (edgeX, edgeY);
|
||||
cg.isRadial = isCorner;
|
||||
|
||||
g.setGradientFill (cg);
|
||||
g.fillRect (area);
|
||||
}
|
||||
|
||||
void DropShadow::drawForRectangle (Graphics& g, const Rectangle<int>& targetArea) const
|
||||
{
|
||||
ColourGradient cg (colour, 0, 0, colour.withAlpha (0.0f), 0, 0, false);
|
||||
|
||||
for (float i = 0.05f; i < 1.0f; i += 0.1f)
|
||||
cg.addColour (1.0 - i, colour.withMultipliedAlpha (i * i));
|
||||
|
||||
const float radiusInset = (float) radius / 2.0f;
|
||||
const float expandedRadius = (float) radius + radiusInset;
|
||||
|
||||
auto area = targetArea.toFloat().reduced (radiusInset) + offset.toFloat();
|
||||
|
||||
auto r = area.expanded (expandedRadius);
|
||||
auto top = r.removeFromTop (expandedRadius);
|
||||
auto bottom = r.removeFromBottom (expandedRadius);
|
||||
|
||||
drawShadowSection (g, cg, top.removeFromLeft (expandedRadius), true, 1.0f, 1.0f, 0, 1.0f);
|
||||
drawShadowSection (g, cg, top.removeFromRight (expandedRadius), true, 0, 1.0f, 1.0f, 1.0f);
|
||||
drawShadowSection (g, cg, top, false, 0, 1.0f, 0, 0);
|
||||
|
||||
drawShadowSection (g, cg, bottom.removeFromLeft (expandedRadius), true, 1.0f, 0, 0, 0);
|
||||
drawShadowSection (g, cg, bottom.removeFromRight (expandedRadius), true, 0, 0, 1.0f, 0);
|
||||
drawShadowSection (g, cg, bottom, false, 0, 0, 0, 1.0f);
|
||||
|
||||
drawShadowSection (g, cg, r.removeFromLeft (expandedRadius), false, 1.0f, 0, 0, 0);
|
||||
drawShadowSection (g, cg, r.removeFromRight (expandedRadius), false, 0, 0, 1.0f, 0);
|
||||
|
||||
g.setColour (colour);
|
||||
g.fillRect (area);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
DropShadowEffect::DropShadowEffect() = default;
|
||||
DropShadowEffect::~DropShadowEffect() = default;
|
||||
|
||||
void DropShadowEffect::setShadowProperties (const DropShadow& newShadow)
|
||||
{
|
||||
shadow = newShadow;
|
||||
}
|
||||
|
||||
void DropShadowEffect::applyEffect (Image& image, Graphics& g, float scaleFactor, float alpha)
|
||||
{
|
||||
DropShadow s (shadow);
|
||||
s.radius = roundToInt ((float) s.radius * scaleFactor);
|
||||
s.colour = s.colour.withMultipliedAlpha (alpha);
|
||||
s.offset.x = roundToInt ((float) s.offset.x * scaleFactor);
|
||||
s.offset.y = roundToInt ((float) s.offset.y * scaleFactor);
|
||||
|
||||
s.drawForImage (g, image);
|
||||
|
||||
g.setOpacity (alpha);
|
||||
g.drawImageAt (image, 0, 0);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Defines a drop-shadow effect.
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
struct JUCE_API DropShadow
|
||||
{
|
||||
/** Creates a default drop-shadow effect. */
|
||||
DropShadow() = default;
|
||||
|
||||
/** Creates a drop-shadow object with the given parameters. */
|
||||
DropShadow (Colour shadowColour, int radius, Point<int> offset) noexcept;
|
||||
|
||||
/** Renders a drop-shadow based on the alpha-channel of the given image. */
|
||||
void drawForImage (Graphics& g, const Image& srcImage) const;
|
||||
|
||||
/** Renders a drop-shadow based on the shape of a path. */
|
||||
void drawForPath (Graphics& g, const Path& path) const;
|
||||
|
||||
/** Renders a drop-shadow for a rectangle.
|
||||
Note that for speed, this approximates the shadow using gradients.
|
||||
*/
|
||||
void drawForRectangle (Graphics& g, const Rectangle<int>& area) const;
|
||||
|
||||
/** The colour with which to render the shadow.
|
||||
In most cases you'll probably want to leave this as black with an alpha
|
||||
value of around 0.5
|
||||
*/
|
||||
Colour colour { 0x90000000 };
|
||||
|
||||
/** The approximate spread of the shadow. */
|
||||
int radius { 4 };
|
||||
|
||||
/** The offset of the shadow. */
|
||||
Point<int> offset;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An effect filter that adds a drop-shadow behind the image's content.
|
||||
|
||||
(This will only work on images/components that aren't opaque, of course).
|
||||
|
||||
When added to a component, this effect will draw a soft-edged
|
||||
shadow based on what gets drawn inside it. The shadow will also
|
||||
be applied to the component's children.
|
||||
|
||||
For speed, this doesn't use a proper gaussian blur, but cheats by
|
||||
using a simple bilinear filter. If you need a really high-quality
|
||||
shadow, check out ImageConvolutionKernel::createGaussianBlur()
|
||||
|
||||
@see Component::setComponentEffect
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API DropShadowEffect : public ImageEffectFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a default drop-shadow effect.
|
||||
To customise the shadow's appearance, use the setShadowProperties() method.
|
||||
*/
|
||||
DropShadowEffect();
|
||||
|
||||
/** Destructor. */
|
||||
~DropShadowEffect() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets up parameters affecting the shadow's appearance. */
|
||||
void setShadowProperties (const DropShadow& newShadow);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void applyEffect (Image& sourceImage, Graphics& destContext, float scaleFactor, float alpha) override;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
DropShadow shadow;
|
||||
|
||||
JUCE_LEAK_DETECTOR (DropShadowEffect)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
GlowEffect::GlowEffect() = default;
|
||||
GlowEffect::~GlowEffect() = default;
|
||||
|
||||
void GlowEffect::setGlowProperties (float newRadius, Colour newColour, Point<int> pos)
|
||||
{
|
||||
radius = newRadius;
|
||||
colour = newColour;
|
||||
offset = pos;
|
||||
}
|
||||
|
||||
void GlowEffect::applyEffect (Image& image, Graphics& g, float scaleFactor, float alpha)
|
||||
{
|
||||
auto blurred = image.createCopy();
|
||||
blurred.setBackupEnabled (false);
|
||||
|
||||
if (auto ptr = blurred.getPixelData())
|
||||
ptr->applyGaussianBlurEffect (radius * scaleFactor);
|
||||
|
||||
g.setColour (colour.withMultipliedAlpha (alpha));
|
||||
g.drawImageAt (blurred, offset.x, offset.y, true);
|
||||
|
||||
g.setOpacity (alpha);
|
||||
g.drawImageAt (image, offset.x, offset.y, false);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component effect that adds a coloured blur around the component's contents.
|
||||
|
||||
(This will only work on non-opaque components).
|
||||
|
||||
@see Component::setComponentEffect, DropShadowEffect
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API GlowEffect : public ImageEffectFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a default 'glow' effect.
|
||||
To customise its appearance, use the setGlowProperties() method.
|
||||
*/
|
||||
GlowEffect();
|
||||
|
||||
/** Destructor. */
|
||||
~GlowEffect() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the glow's radius and colour.
|
||||
|
||||
The radius is how large the blur should be, and the colour is
|
||||
used to render it (for a less intense glow, lower the colour's
|
||||
opacity).
|
||||
*/
|
||||
void setGlowProperties (float newRadius,
|
||||
Colour newColour,
|
||||
Point<int> offset = {});
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void applyEffect (Image&, Graphics&, float scaleFactor, float alpha) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
float radius = 2.0f;
|
||||
Colour colour { Colours::white };
|
||||
Point<int> offset;
|
||||
|
||||
JUCE_LEAK_DETECTOR (GlowEffect)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE framework.
|
||||
Copyright (c) Raw Material Software Limited
|
||||
|
||||
JUCE is an open source framework subject to commercial or open source
|
||||
licensing.
|
||||
|
||||
By downloading, installing, or using the JUCE framework, or combining the
|
||||
JUCE framework with any other source code, object code, content or any other
|
||||
copyrightable work, you agree to the terms of the JUCE End User Licence
|
||||
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
||||
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
||||
do not agree to the terms of these agreements, we will not license the JUCE
|
||||
framework to you, and you must discontinue the installation or download
|
||||
process and cease use of the JUCE framework.
|
||||
|
||||
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
||||
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
||||
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
||||
|
||||
Or:
|
||||
|
||||
You may also use this code under the terms of the AGPLv3:
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
||||
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
||||
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A graphical effect filter that can be applied to components.
|
||||
|
||||
An ImageEffectFilter can be applied to the image that a component
|
||||
paints before it hits the screen.
|
||||
|
||||
This is used for adding effects like shadows, blurs, etc.
|
||||
|
||||
@see Component::setComponentEffect
|
||||
|
||||
@tags{Graphics}
|
||||
*/
|
||||
class JUCE_API ImageEffectFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Overridden to render the effect.
|
||||
|
||||
The implementation of this method must use the image that is passed in
|
||||
as its source, and should render its output to the graphics context passed in.
|
||||
|
||||
@param sourceImage the image that the source component has just rendered with
|
||||
its paint() method. The image may or may not have an alpha
|
||||
channel, depending on whether the component is opaque.
|
||||
@param destContext the graphics context to use to draw the resultant image.
|
||||
@param scaleFactor a scale factor that has been applied to the image - e.g. if
|
||||
this is 2, then the image is actually scaled-up to twice the
|
||||
original resolution
|
||||
@param alpha the alpha with which to draw the resultant image to the
|
||||
target context
|
||||
*/
|
||||
virtual void applyEffect (Image& sourceImage,
|
||||
Graphics& destContext,
|
||||
float scaleFactor,
|
||||
float alpha) = 0;
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ImageEffectFilter() = default;
|
||||
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,42 @@
|
||||
HarfBuzz is licensed under the so-called "Old MIT" license. Details follow.
|
||||
For parts of HarfBuzz that are licensed under different licenses see individual
|
||||
files names COPYING in subdirectories where applicable.
|
||||
|
||||
Copyright © 2010-2022 Google, Inc.
|
||||
Copyright © 2015-2020 Ebrahim Byagowi
|
||||
Copyright © 2019,2020 Facebook, Inc.
|
||||
Copyright © 2012,2015 Mozilla Foundation
|
||||
Copyright © 2011 Codethink Limited
|
||||
Copyright © 2008,2010 Nokia Corporation and/or its subsidiary(-ies)
|
||||
Copyright © 2009 Keith Stribley
|
||||
Copyright © 2011 Martin Hosken and SIL International
|
||||
Copyright © 2007 Chris Wilson
|
||||
Copyright © 2005,2006,2020,2021,2022,2023 Behdad Esfahbod
|
||||
Copyright © 2004,2007,2008,2009,2010,2013,2021,2022,2023 Red Hat, Inc.
|
||||
Copyright © 1998-2005 David Turner and Werner Lemberg
|
||||
Copyright © 2016 Igalia S.L.
|
||||
Copyright © 2022 Matthias Clasen
|
||||
Copyright © 2018,2021 Khaled Hosny
|
||||
Copyright © 2018,2019,2020 Adobe, Inc
|
||||
Copyright © 2013-2015 Alexei Podtelezhnikov
|
||||
|
||||
For full copyright notices consult the individual files in the package.
|
||||
|
||||
|
||||
Permission is hereby granted, without written agreement and without
|
||||
license or royalty fees, to use, copy, modify, and distribute this
|
||||
software and its documentation for any purpose, provided that the
|
||||
above copyright notice and the following two paragraphs appear in
|
||||
all copies of this software.
|
||||
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGE.
|
||||
|
||||
THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+137
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright © 2018 Ebrahim Byagowi
|
||||
* Copyright © 2020 Google, Inc.
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef OT_COLOR_COLR_COLRV1_CLOSURE_HH
|
||||
#define OT_COLOR_COLR_COLRV1_CLOSURE_HH
|
||||
|
||||
#include "../../../hb-open-type.hh"
|
||||
#include "COLR.hh"
|
||||
|
||||
/*
|
||||
* COLR -- Color
|
||||
* https://docs.microsoft.com/en-us/typography/opentype/spec/colr
|
||||
*/
|
||||
namespace OT {
|
||||
|
||||
HB_INTERNAL void PaintColrLayers::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
c->add_layer_indices (firstLayerIndex, numLayers);
|
||||
const LayerList &paint_offset_lists = c->get_colr_table ()->get_layerList ();
|
||||
for (unsigned i = firstLayerIndex; i < firstLayerIndex + numLayers; i++)
|
||||
{
|
||||
const Paint &paint = std::addressof (paint_offset_lists) + paint_offset_lists[i];
|
||||
paint.dispatch (c);
|
||||
}
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintGlyph::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
c->add_glyph (gid);
|
||||
(this+paint).dispatch (c);
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintColrGlyph::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
const COLR *colr_table = c->get_colr_table ();
|
||||
const BaseGlyphPaintRecord* baseglyph_paintrecord = colr_table->get_base_glyph_paintrecord (gid);
|
||||
if (!baseglyph_paintrecord) return;
|
||||
c->add_glyph (gid);
|
||||
|
||||
const BaseGlyphList &baseglyph_list = colr_table->get_baseglyphList ();
|
||||
(&baseglyph_list+baseglyph_paintrecord->paint).dispatch (c);
|
||||
}
|
||||
|
||||
template <template<typename> class Var>
|
||||
HB_INTERNAL void PaintTransform<Var>::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
(this+src).dispatch (c);
|
||||
(this+transform).closurev1 (c);
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintTranslate::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
(this+src).dispatch (c);
|
||||
c->num_var_idxes = 2;
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintScale::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
(this+src).dispatch (c);
|
||||
c->num_var_idxes = 2;
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintScaleAroundCenter::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
(this+src).dispatch (c);
|
||||
c->num_var_idxes = 4;
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintScaleUniform::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
(this+src).dispatch (c);
|
||||
c->num_var_idxes = 1;
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintScaleUniformAroundCenter::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
(this+src).dispatch (c);
|
||||
c->num_var_idxes = 3;
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintRotate::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
(this+src).dispatch (c);
|
||||
c->num_var_idxes = 1;
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintRotateAroundCenter::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
(this+src).dispatch (c);
|
||||
c->num_var_idxes = 3;
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintSkew::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
(this+src).dispatch (c);
|
||||
c->num_var_idxes = 2;
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintSkewAroundCenter::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
(this+src).dispatch (c);
|
||||
c->num_var_idxes = 4;
|
||||
}
|
||||
|
||||
HB_INTERNAL void PaintComposite::closurev1 (hb_colrv1_closure_context_t* c) const
|
||||
{
|
||||
(this+src).dispatch (c);
|
||||
(this+backdrop).dispatch (c);
|
||||
}
|
||||
|
||||
} /* namespace OT */
|
||||
|
||||
|
||||
#endif /* OT_COLOR_COLR_COLRV1_CLOSURE_HH */
|
||||
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright © 2016 Google, Inc.
|
||||
* Copyright © 2018 Ebrahim Byagowi
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
* Google Author(s): Sascha Brawer
|
||||
*/
|
||||
|
||||
#ifndef OT_COLOR_CPAL_CPAL_HH
|
||||
#define OT_COLOR_CPAL_CPAL_HH
|
||||
|
||||
#include "../../../hb-open-type.hh"
|
||||
#include "../../../hb-ot-color.h"
|
||||
#include "../../../hb-ot-name.h"
|
||||
|
||||
|
||||
/*
|
||||
* CPAL -- Color Palette
|
||||
* https://docs.microsoft.com/en-us/typography/opentype/spec/cpal
|
||||
*/
|
||||
#define HB_OT_TAG_CPAL HB_TAG('C','P','A','L')
|
||||
|
||||
namespace OT {
|
||||
|
||||
|
||||
struct CPALV1Tail
|
||||
{
|
||||
friend struct CPAL;
|
||||
|
||||
private:
|
||||
hb_ot_color_palette_flags_t get_palette_flags (const void *base,
|
||||
unsigned int palette_index,
|
||||
unsigned int palette_count) const
|
||||
{
|
||||
if (!paletteFlagsZ) return HB_OT_COLOR_PALETTE_FLAG_DEFAULT;
|
||||
return (hb_ot_color_palette_flags_t) (uint32_t)
|
||||
(base+paletteFlagsZ).as_array (palette_count)[palette_index];
|
||||
}
|
||||
|
||||
hb_ot_name_id_t get_palette_name_id (const void *base,
|
||||
unsigned int palette_index,
|
||||
unsigned int palette_count) const
|
||||
{
|
||||
if (!paletteLabelsZ) return HB_OT_NAME_ID_INVALID;
|
||||
return (base+paletteLabelsZ).as_array (palette_count)[palette_index];
|
||||
}
|
||||
|
||||
hb_ot_name_id_t get_color_name_id (const void *base,
|
||||
unsigned int color_index,
|
||||
unsigned int color_count) const
|
||||
{
|
||||
if (!colorLabelsZ) return HB_OT_NAME_ID_INVALID;
|
||||
return (base+colorLabelsZ).as_array (color_count)[color_index];
|
||||
}
|
||||
|
||||
public:
|
||||
void collect_name_ids (const void *base,
|
||||
unsigned palette_count,
|
||||
unsigned color_count,
|
||||
const hb_map_t *color_index_map,
|
||||
hb_set_t *nameids_to_retain /* OUT */) const
|
||||
{
|
||||
if (paletteLabelsZ)
|
||||
{
|
||||
+ (base+paletteLabelsZ).as_array (palette_count)
|
||||
| hb_sink (nameids_to_retain)
|
||||
;
|
||||
}
|
||||
|
||||
if (colorLabelsZ)
|
||||
{
|
||||
const hb_array_t<const NameID> colorLabels = (base+colorLabelsZ).as_array (color_count);
|
||||
for (unsigned i = 0; i < color_count; i++)
|
||||
{
|
||||
if (!color_index_map->has (i)) continue;
|
||||
nameids_to_retain->add (colorLabels[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
unsigned palette_count,
|
||||
unsigned color_count,
|
||||
const void *base,
|
||||
const hb_map_t *color_index_map) const
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
auto *out = c->allocate_size<CPALV1Tail> (static_size);
|
||||
if (unlikely (!out)) return_trace (false);
|
||||
|
||||
out->paletteFlagsZ = 0;
|
||||
if (paletteFlagsZ)
|
||||
out->paletteFlagsZ.serialize_copy (c, paletteFlagsZ, base, 0, hb_serialize_context_t::Head, palette_count);
|
||||
|
||||
out->paletteLabelsZ = 0;
|
||||
if (paletteLabelsZ)
|
||||
out->paletteLabelsZ.serialize_copy (c, paletteLabelsZ, base, 0, hb_serialize_context_t::Head, palette_count);
|
||||
|
||||
const hb_array_t<const NameID> colorLabels = (base+colorLabelsZ).as_array (color_count);
|
||||
if (colorLabelsZ)
|
||||
{
|
||||
c->push ();
|
||||
for (unsigned i = 0; i < color_count; i++)
|
||||
{
|
||||
if (!color_index_map->has (i)) continue;
|
||||
if (!c->copy<NameID> (colorLabels[i]))
|
||||
{
|
||||
c->pop_discard ();
|
||||
return_trace (false);
|
||||
}
|
||||
}
|
||||
c->add_link (out->colorLabelsZ, c->pop_pack ());
|
||||
}
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c,
|
||||
const void *base,
|
||||
unsigned int palette_count,
|
||||
unsigned int color_count) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this) &&
|
||||
(!paletteFlagsZ || (base+paletteFlagsZ).sanitize (c, palette_count)) &&
|
||||
(!paletteLabelsZ || (base+paletteLabelsZ).sanitize (c, palette_count)) &&
|
||||
(!colorLabelsZ || (base+colorLabelsZ).sanitize (c, color_count)));
|
||||
}
|
||||
|
||||
protected:
|
||||
// TODO(garretrieger): these offsets can hold nulls so we should not be using non-null offsets
|
||||
// here. Currently they are needed since UnsizedArrayOf doesn't define null_size
|
||||
NNOffset32To<UnsizedArrayOf<HBUINT32>>
|
||||
paletteFlagsZ; /* Offset from the beginning of CPAL table to
|
||||
* the Palette Type Array. Set to 0 if no array
|
||||
* is provided. */
|
||||
NNOffset32To<UnsizedArrayOf<NameID>>
|
||||
paletteLabelsZ; /* Offset from the beginning of CPAL table to
|
||||
* the palette labels array. Set to 0 if no
|
||||
* array is provided. */
|
||||
NNOffset32To<UnsizedArrayOf<NameID>>
|
||||
colorLabelsZ; /* Offset from the beginning of CPAL table to
|
||||
* the color labels array. Set to 0
|
||||
* if no array is provided. */
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (12);
|
||||
};
|
||||
|
||||
typedef HBUINT32 BGRAColor;
|
||||
|
||||
struct CPAL
|
||||
{
|
||||
static constexpr hb_tag_t tableTag = HB_OT_TAG_CPAL;
|
||||
|
||||
bool has_data () const { return numPalettes; }
|
||||
|
||||
unsigned int get_size () const
|
||||
{ return min_size + numPalettes * sizeof (colorRecordIndicesZ[0]); }
|
||||
|
||||
unsigned int get_palette_count () const { return numPalettes; }
|
||||
unsigned int get_color_count () const { return numColors; }
|
||||
|
||||
hb_ot_color_palette_flags_t get_palette_flags (unsigned int palette_index) const
|
||||
{ return v1 ().get_palette_flags (this, palette_index, numPalettes); }
|
||||
|
||||
hb_ot_name_id_t get_palette_name_id (unsigned int palette_index) const
|
||||
{ return v1 ().get_palette_name_id (this, palette_index, numPalettes); }
|
||||
|
||||
hb_ot_name_id_t get_color_name_id (unsigned int color_index) const
|
||||
{ return v1 ().get_color_name_id (this, color_index, numColors); }
|
||||
|
||||
unsigned int get_palette_colors (unsigned int palette_index,
|
||||
unsigned int start_offset,
|
||||
unsigned int *color_count, /* IN/OUT. May be NULL. */
|
||||
hb_color_t *colors /* OUT. May be NULL. */) const
|
||||
{
|
||||
if (unlikely (palette_index >= numPalettes))
|
||||
{
|
||||
if (color_count) *color_count = 0;
|
||||
return 0;
|
||||
}
|
||||
unsigned int start_index = colorRecordIndicesZ[palette_index];
|
||||
hb_array_t<const BGRAColor> all_colors ((this+colorRecordsZ).arrayZ, numColorRecords);
|
||||
hb_array_t<const BGRAColor> palette_colors = all_colors.sub_array (start_index,
|
||||
numColors);
|
||||
if (color_count)
|
||||
{
|
||||
+ palette_colors.sub_array (start_offset, color_count)
|
||||
| hb_sink (hb_array (colors, *color_count))
|
||||
;
|
||||
}
|
||||
return numColors;
|
||||
}
|
||||
|
||||
void collect_name_ids (const hb_map_t *color_index_map,
|
||||
hb_set_t *nameids_to_retain /* OUT */) const
|
||||
{
|
||||
if (version == 1)
|
||||
{
|
||||
hb_barrier ();
|
||||
v1 ().collect_name_ids (this, numPalettes, numColors, color_index_map, nameids_to_retain);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const CPALV1Tail& v1 () const
|
||||
{
|
||||
if (version == 0) return Null (CPALV1Tail);
|
||||
hb_barrier ();
|
||||
return StructAfter<CPALV1Tail> (*this);
|
||||
}
|
||||
|
||||
public:
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
const hb_array_t<const HBUINT16> &color_record_indices,
|
||||
const hb_array_t<const BGRAColor> &color_records,
|
||||
const hb_vector_t<unsigned>& first_color_index_for_layer,
|
||||
const hb_map_t& first_color_to_layer_index,
|
||||
const hb_set_t &retained_color_indices) const
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
|
||||
// TODO(grieger): limit total final size.
|
||||
|
||||
for (const auto idx : color_record_indices)
|
||||
{
|
||||
hb_codepoint_t layer_index = first_color_to_layer_index[idx];
|
||||
|
||||
HBUINT16 new_idx;
|
||||
new_idx = layer_index * retained_color_indices.get_population ();
|
||||
if (!c->copy<HBUINT16> (new_idx)) return_trace (false);
|
||||
}
|
||||
|
||||
c->push ();
|
||||
for (unsigned first_color_index : first_color_index_for_layer)
|
||||
{
|
||||
for (hb_codepoint_t color_index : retained_color_indices)
|
||||
{
|
||||
if (!c->copy<BGRAColor> (color_records[first_color_index + color_index]))
|
||||
{
|
||||
c->pop_discard ();
|
||||
return_trace (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c->add_link (colorRecordsZ, c->pop_pack ());
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
if (!numPalettes) return_trace (false);
|
||||
|
||||
const hb_map_t *color_index_map = &c->plan->colr_palettes;
|
||||
if (color_index_map->is_empty ()) return_trace (false);
|
||||
|
||||
hb_set_t retained_color_indices;
|
||||
for (const auto _ : color_index_map->keys ())
|
||||
{
|
||||
if (_ == 0xFFFF) continue;
|
||||
retained_color_indices.add (_);
|
||||
}
|
||||
if (retained_color_indices.is_empty ()) return_trace (false);
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
|
||||
|
||||
out->version = version;
|
||||
out->numColors = retained_color_indices.get_population ();
|
||||
out->numPalettes = numPalettes;
|
||||
|
||||
hb_vector_t<unsigned> first_color_index_for_layer;
|
||||
hb_map_t first_color_to_layer_index;
|
||||
|
||||
const hb_array_t<const HBUINT16> colorRecordIndices = colorRecordIndicesZ.as_array (numPalettes);
|
||||
for (const auto first_color_record_idx : colorRecordIndices)
|
||||
{
|
||||
if (first_color_to_layer_index.has (first_color_record_idx)) continue;
|
||||
|
||||
first_color_index_for_layer.push (first_color_record_idx);
|
||||
first_color_to_layer_index.set (first_color_record_idx,
|
||||
first_color_index_for_layer.length - 1);
|
||||
}
|
||||
|
||||
out->numColorRecords = first_color_index_for_layer.length
|
||||
* retained_color_indices.get_population ();
|
||||
|
||||
const hb_array_t<const BGRAColor> color_records = (this+colorRecordsZ).as_array (numColorRecords);
|
||||
if (!out->serialize (c->serializer,
|
||||
colorRecordIndices,
|
||||
color_records,
|
||||
first_color_index_for_layer,
|
||||
first_color_to_layer_index,
|
||||
retained_color_indices))
|
||||
return_trace (false);
|
||||
|
||||
if (version == 1)
|
||||
{
|
||||
hb_barrier ();
|
||||
return_trace (v1 ().serialize (c->serializer, numPalettes, numColors, this, color_index_map));
|
||||
}
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this) &&
|
||||
hb_barrier () &&
|
||||
(this+colorRecordsZ).sanitize (c, numColorRecords) &&
|
||||
colorRecordIndicesZ.sanitize (c, numPalettes) &&
|
||||
(version == 0 || v1 ().sanitize (c, this, numPalettes, numColors)));
|
||||
}
|
||||
|
||||
protected:
|
||||
HBUINT16 version; /* Table version number */
|
||||
/* Version 0 */
|
||||
HBUINT16 numColors; /* Number of colors in each palette. */
|
||||
HBUINT16 numPalettes; /* Number of palettes in the table. */
|
||||
HBUINT16 numColorRecords; /* Total number of color records, combined for
|
||||
* all palettes. */
|
||||
NNOffset32To<UnsizedArrayOf<BGRAColor>>
|
||||
colorRecordsZ; /* Offset from the beginning of CPAL table to
|
||||
* the first ColorRecord. */
|
||||
UnsizedArrayOf<HBUINT16>
|
||||
colorRecordIndicesZ; /* Index of each palette’s first color record in
|
||||
* the combined color record array. */
|
||||
/*CPALV1Tail v1;*/
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (12, colorRecordIndicesZ);
|
||||
};
|
||||
|
||||
} /* namespace OT */
|
||||
|
||||
|
||||
#endif /* OT_COLOR_CPAL_CPAL_HH */
|
||||
@@ -0,0 +1,448 @@
|
||||
/*
|
||||
* Copyright © 2018 Ebrahim Byagowi
|
||||
* Copyright © 2020 Google, Inc.
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
* Google Author(s): Calder Kitagawa
|
||||
*/
|
||||
|
||||
#ifndef OT_COLOR_SBIX_SBIX_HH
|
||||
#define OT_COLOR_SBIX_SBIX_HH
|
||||
|
||||
#include "../../../hb-open-type.hh"
|
||||
#include "../../../hb-paint.hh"
|
||||
|
||||
/*
|
||||
* sbix -- Standard Bitmap Graphics
|
||||
* https://docs.microsoft.com/en-us/typography/opentype/spec/sbix
|
||||
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6sbix.html
|
||||
*/
|
||||
#define HB_OT_TAG_sbix HB_TAG('s','b','i','x')
|
||||
|
||||
|
||||
namespace OT {
|
||||
|
||||
|
||||
struct SBIXGlyph
|
||||
{
|
||||
SBIXGlyph* copy (hb_serialize_context_t *c, unsigned int data_length) const
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
SBIXGlyph* new_glyph = c->start_embed<SBIXGlyph> ();
|
||||
if (unlikely (!c->extend_min (new_glyph))) return_trace (nullptr);
|
||||
|
||||
new_glyph->xOffset = xOffset;
|
||||
new_glyph->yOffset = yOffset;
|
||||
new_glyph->graphicType = graphicType;
|
||||
data.copy (c, data_length);
|
||||
return_trace (new_glyph);
|
||||
}
|
||||
|
||||
HBINT16 xOffset; /* The horizontal (x-axis) offset from the left
|
||||
* edge of the graphic to the glyph’s origin.
|
||||
* That is, the x-coordinate of the point on the
|
||||
* baseline at the left edge of the glyph. */
|
||||
HBINT16 yOffset; /* The vertical (y-axis) offset from the bottom
|
||||
* edge of the graphic to the glyph’s origin.
|
||||
* That is, the y-coordinate of the point on the
|
||||
* baseline at the left edge of the glyph. */
|
||||
Tag graphicType; /* Indicates the format of the embedded graphic
|
||||
* data: one of 'jpg ', 'png ' or 'tiff', or the
|
||||
* special format 'dupe'. */
|
||||
UnsizedArrayOf<HBUINT8>
|
||||
data; /* The actual embedded graphic data. The total
|
||||
* length is inferred from sequential entries in
|
||||
* the glyphDataOffsets array and the fixed size
|
||||
* (8 bytes) of the preceding fields. */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (8, data);
|
||||
};
|
||||
|
||||
struct SBIXStrike
|
||||
{
|
||||
static unsigned int get_size (unsigned num_glyphs)
|
||||
{ return min_size + num_glyphs * HBUINT32::static_size; }
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this) &&
|
||||
imageOffsetsZ.sanitize_shallow (c, c->get_num_glyphs () + 1));
|
||||
}
|
||||
|
||||
hb_blob_t *get_glyph_blob (unsigned int glyph_id,
|
||||
hb_blob_t *sbix_blob,
|
||||
hb_tag_t file_type,
|
||||
int *x_offset,
|
||||
int *y_offset,
|
||||
unsigned int num_glyphs,
|
||||
unsigned int *strike_ppem) const
|
||||
{
|
||||
if (unlikely (!ppem)) return hb_blob_get_empty (); /* To get Null() object out of the way. */
|
||||
|
||||
unsigned int retry_count = 8;
|
||||
unsigned int sbix_len = sbix_blob->length;
|
||||
unsigned int strike_offset = (const char *) this - (const char *) sbix_blob->data;
|
||||
assert (strike_offset < sbix_len);
|
||||
|
||||
retry:
|
||||
if (unlikely (glyph_id >= num_glyphs ||
|
||||
imageOffsetsZ[glyph_id + 1] <= imageOffsetsZ[glyph_id] ||
|
||||
imageOffsetsZ[glyph_id + 1] - imageOffsetsZ[glyph_id] <= SBIXGlyph::min_size ||
|
||||
(unsigned int) imageOffsetsZ[glyph_id + 1] > sbix_len - strike_offset))
|
||||
return hb_blob_get_empty ();
|
||||
|
||||
unsigned int glyph_offset = strike_offset + (unsigned int) imageOffsetsZ[glyph_id] + SBIXGlyph::min_size;
|
||||
unsigned int glyph_length = imageOffsetsZ[glyph_id + 1] - imageOffsetsZ[glyph_id] - SBIXGlyph::min_size;
|
||||
|
||||
const SBIXGlyph *glyph = &(this+imageOffsetsZ[glyph_id]);
|
||||
|
||||
if (glyph->graphicType == HB_TAG ('d','u','p','e'))
|
||||
{
|
||||
if (glyph_length >= 2)
|
||||
{
|
||||
glyph_id = *((HBUINT16 *) &glyph->data);
|
||||
if (retry_count--)
|
||||
goto retry;
|
||||
}
|
||||
return hb_blob_get_empty ();
|
||||
}
|
||||
|
||||
if (unlikely (file_type != glyph->graphicType))
|
||||
return hb_blob_get_empty ();
|
||||
|
||||
if (strike_ppem) *strike_ppem = ppem;
|
||||
if (x_offset) *x_offset = glyph->xOffset;
|
||||
if (y_offset) *y_offset = glyph->yOffset;
|
||||
return hb_blob_create_sub_blob (sbix_blob, glyph_offset, glyph_length);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c, unsigned int available_len) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
unsigned int num_output_glyphs = c->plan->num_output_glyphs ();
|
||||
|
||||
auto* out = c->serializer->start_embed<SBIXStrike> ();
|
||||
auto snap = c->serializer->snapshot ();
|
||||
if (unlikely (!c->serializer->extend (out, num_output_glyphs + 1))) return_trace (false);
|
||||
out->ppem = ppem;
|
||||
out->resolution = resolution;
|
||||
HBUINT32 head;
|
||||
head = get_size (num_output_glyphs + 1);
|
||||
|
||||
bool has_glyphs = false;
|
||||
for (unsigned new_gid = 0; new_gid < num_output_glyphs; new_gid++)
|
||||
{
|
||||
hb_codepoint_t old_gid;
|
||||
if (!c->plan->old_gid_for_new_gid (new_gid, &old_gid) ||
|
||||
unlikely (imageOffsetsZ[old_gid].is_null () ||
|
||||
imageOffsetsZ[old_gid + 1].is_null () ||
|
||||
imageOffsetsZ[old_gid + 1] <= imageOffsetsZ[old_gid] ||
|
||||
imageOffsetsZ[old_gid + 1] - imageOffsetsZ[old_gid] <= SBIXGlyph::min_size) ||
|
||||
(unsigned int) imageOffsetsZ[old_gid + 1] > available_len)
|
||||
{
|
||||
out->imageOffsetsZ[new_gid] = head;
|
||||
continue;
|
||||
}
|
||||
has_glyphs = true;
|
||||
unsigned int delta = imageOffsetsZ[old_gid + 1] - imageOffsetsZ[old_gid];
|
||||
unsigned int glyph_data_length = delta - SBIXGlyph::min_size;
|
||||
if (!(this+imageOffsetsZ[old_gid]).copy (c->serializer, glyph_data_length))
|
||||
return_trace (false);
|
||||
out->imageOffsetsZ[new_gid] = head;
|
||||
head += delta;
|
||||
}
|
||||
if (has_glyphs)
|
||||
out->imageOffsetsZ[num_output_glyphs] = head;
|
||||
else
|
||||
c->serializer->revert (snap);
|
||||
return_trace (has_glyphs);
|
||||
}
|
||||
|
||||
public:
|
||||
HBUINT16 ppem; /* The PPEM size for which this strike was designed. */
|
||||
HBUINT16 resolution; /* The device pixel density (in PPI) for which this
|
||||
* strike was designed. (E.g., 96 PPI, 192 PPI.) */
|
||||
protected:
|
||||
UnsizedArrayOf<Offset32To<SBIXGlyph>>
|
||||
imageOffsetsZ; /* Offset from the beginning of the strike data header
|
||||
* to bitmap data for an individual glyph ID. */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (4, imageOffsetsZ);
|
||||
};
|
||||
|
||||
struct sbix
|
||||
{
|
||||
static constexpr hb_tag_t tableTag = HB_OT_TAG_sbix;
|
||||
|
||||
bool has_data () const { return version; }
|
||||
|
||||
const SBIXStrike &get_strike (unsigned int i) const { return this+strikes[i]; }
|
||||
|
||||
struct accelerator_t
|
||||
{
|
||||
accelerator_t (hb_face_t *face)
|
||||
{
|
||||
table = hb_sanitize_context_t ().reference_table<sbix> (face);
|
||||
num_glyphs = face->get_num_glyphs ();
|
||||
}
|
||||
~accelerator_t () { table.destroy (); }
|
||||
|
||||
bool has_data () const { return table->has_data (); }
|
||||
|
||||
bool get_extents (hb_font_t *font,
|
||||
hb_codepoint_t glyph,
|
||||
hb_glyph_extents_t *extents,
|
||||
bool scale = true) const
|
||||
{
|
||||
/* We only support PNG right now, and following function checks type. */
|
||||
return get_png_extents (font, glyph, extents, scale);
|
||||
}
|
||||
|
||||
hb_blob_t *reference_png (hb_font_t *font,
|
||||
hb_codepoint_t glyph_id,
|
||||
int *x_offset,
|
||||
int *y_offset,
|
||||
unsigned int *available_ppem) const
|
||||
{
|
||||
return choose_strike (font).get_glyph_blob (glyph_id, table.get_blob (),
|
||||
HB_TAG ('p','n','g',' '),
|
||||
x_offset, y_offset,
|
||||
num_glyphs, available_ppem);
|
||||
}
|
||||
|
||||
bool paint_glyph (hb_font_t *font, hb_codepoint_t glyph, hb_paint_funcs_t *funcs, void *data) const
|
||||
{
|
||||
if (!has_data ())
|
||||
return false;
|
||||
|
||||
int x_offset = 0, y_offset = 0;
|
||||
unsigned int strike_ppem = 0;
|
||||
hb_blob_t *blob = reference_png (font, glyph, &x_offset, &y_offset, &strike_ppem);
|
||||
hb_glyph_extents_t extents;
|
||||
hb_glyph_extents_t pixel_extents;
|
||||
|
||||
if (blob == hb_blob_get_empty ())
|
||||
return false;
|
||||
|
||||
if (!hb_font_get_glyph_extents (font, glyph, &extents))
|
||||
return false;
|
||||
|
||||
if (unlikely (!get_extents (font, glyph, &pixel_extents, false)))
|
||||
return false;
|
||||
|
||||
bool ret = funcs->image (data,
|
||||
blob,
|
||||
pixel_extents.width, -pixel_extents.height,
|
||||
HB_PAINT_IMAGE_FORMAT_PNG,
|
||||
font->slant_xy,
|
||||
&extents);
|
||||
|
||||
hb_blob_destroy (blob);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
const SBIXStrike &choose_strike (hb_font_t *font) const
|
||||
{
|
||||
unsigned count = table->strikes.len;
|
||||
if (unlikely (!count))
|
||||
return Null (SBIXStrike);
|
||||
|
||||
unsigned int requested_ppem = hb_max (font->x_ppem, font->y_ppem);
|
||||
if (!requested_ppem)
|
||||
requested_ppem = 1<<30; /* Choose largest strike. */
|
||||
/* TODO Add DPI sensitivity as well? */
|
||||
unsigned int best_i = 0;
|
||||
unsigned int best_ppem = table->get_strike (0).ppem;
|
||||
|
||||
for (unsigned int i = 1; i < count; i++)
|
||||
{
|
||||
unsigned int ppem = (table->get_strike (i)).ppem;
|
||||
if ((requested_ppem <= ppem && ppem < best_ppem) ||
|
||||
(requested_ppem > best_ppem && ppem > best_ppem))
|
||||
{
|
||||
best_i = i;
|
||||
best_ppem = ppem;
|
||||
}
|
||||
}
|
||||
|
||||
return table->get_strike (best_i);
|
||||
}
|
||||
|
||||
struct PNGHeader
|
||||
{
|
||||
HBUINT8 signature[8];
|
||||
struct
|
||||
{
|
||||
struct
|
||||
{
|
||||
HBUINT32 length;
|
||||
Tag type;
|
||||
} header;
|
||||
HBUINT32 width;
|
||||
HBUINT32 height;
|
||||
HBUINT8 bitDepth;
|
||||
HBUINT8 colorType;
|
||||
HBUINT8 compressionMethod;
|
||||
HBUINT8 filterMethod;
|
||||
HBUINT8 interlaceMethod;
|
||||
} IHDR;
|
||||
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (29);
|
||||
};
|
||||
|
||||
bool get_png_extents (hb_font_t *font,
|
||||
hb_codepoint_t glyph,
|
||||
hb_glyph_extents_t *extents,
|
||||
bool scale = true) const
|
||||
{
|
||||
/* Following code is safe to call even without data.
|
||||
* But faster to short-circuit. */
|
||||
if (!has_data ())
|
||||
return false;
|
||||
|
||||
int x_offset = 0, y_offset = 0;
|
||||
unsigned int strike_ppem = 0;
|
||||
hb_blob_t *blob = reference_png (font, glyph, &x_offset, &y_offset, &strike_ppem);
|
||||
|
||||
const PNGHeader &png = *blob->as<PNGHeader>();
|
||||
|
||||
if (png.IHDR.height >= 65536 || png.IHDR.width >= 65536)
|
||||
{
|
||||
hb_blob_destroy (blob);
|
||||
return false;
|
||||
}
|
||||
|
||||
extents->x_bearing = x_offset;
|
||||
extents->y_bearing = png.IHDR.height + y_offset;
|
||||
extents->width = png.IHDR.width;
|
||||
extents->height = -1 * png.IHDR.height;
|
||||
|
||||
/* Convert to font units. */
|
||||
if (strike_ppem && scale)
|
||||
{
|
||||
float scale = font->face->get_upem () / (float) strike_ppem;
|
||||
extents->x_bearing = roundf (extents->x_bearing * scale);
|
||||
extents->y_bearing = roundf (extents->y_bearing * scale);
|
||||
extents->width = roundf (extents->width * scale);
|
||||
extents->height = roundf (extents->height * scale);
|
||||
}
|
||||
|
||||
if (scale)
|
||||
font->scale_glyph_extents (extents);
|
||||
|
||||
hb_blob_destroy (blob);
|
||||
|
||||
return strike_ppem;
|
||||
}
|
||||
|
||||
private:
|
||||
hb_blob_ptr_t<sbix> table;
|
||||
|
||||
unsigned int num_glyphs;
|
||||
};
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (likely (c->check_struct (this) &&
|
||||
hb_barrier () &&
|
||||
version >= 1 &&
|
||||
strikes.sanitize (c, this)));
|
||||
}
|
||||
|
||||
bool
|
||||
add_strike (hb_subset_context_t *c, unsigned i) const
|
||||
{
|
||||
if (strikes[i].is_null () || c->source_blob->length < (unsigned) strikes[i])
|
||||
return false;
|
||||
|
||||
return (this+strikes[i]).subset (c, c->source_blob->length - (unsigned) strikes[i]);
|
||||
}
|
||||
|
||||
bool serialize_strike_offsets (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
|
||||
auto *out = c->serializer->start_embed<Array32OfOffset32To<SBIXStrike>> ();
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
|
||||
hb_vector_t<Offset32To<SBIXStrike>*> new_strikes;
|
||||
hb_vector_t<hb_serialize_context_t::objidx_t> objidxs;
|
||||
for (int i = strikes.len - 1; i >= 0; --i)
|
||||
{
|
||||
auto* o = out->serialize_append (c->serializer);
|
||||
if (unlikely (!o)) return_trace (false);
|
||||
*o = 0;
|
||||
auto snap = c->serializer->snapshot ();
|
||||
c->serializer->push ();
|
||||
bool ret = add_strike (c, i);
|
||||
if (!ret)
|
||||
{
|
||||
c->serializer->pop_discard ();
|
||||
out->pop ();
|
||||
c->serializer->revert (snap);
|
||||
}
|
||||
else
|
||||
{
|
||||
objidxs.push (c->serializer->pop_pack ());
|
||||
new_strikes.push (o);
|
||||
}
|
||||
}
|
||||
for (unsigned int i = 0; i < new_strikes.length; ++i)
|
||||
c->serializer->add_link (*new_strikes[i], objidxs[new_strikes.length - 1 - i]);
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t* c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
|
||||
if (unlikely (!c->serializer->embed (this->version))) return_trace (false);
|
||||
if (unlikely (!c->serializer->embed (this->flags))) return_trace (false);
|
||||
|
||||
return_trace (serialize_strike_offsets (c));
|
||||
}
|
||||
|
||||
protected:
|
||||
HBUINT16 version; /* Table version number — set to 1 */
|
||||
HBUINT16 flags; /* Bit 0: Set to 1. Bit 1: Draw outlines.
|
||||
* Bits 2 to 15: reserved (set to 0). */
|
||||
Array32OfOffset32To<SBIXStrike>
|
||||
strikes; /* Offsets from the beginning of the 'sbix'
|
||||
* table to data for each individual bitmap strike. */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (8, strikes);
|
||||
};
|
||||
|
||||
struct sbix_accelerator_t : sbix::accelerator_t {
|
||||
sbix_accelerator_t (hb_face_t *face) : sbix::accelerator_t (face) {}
|
||||
};
|
||||
|
||||
|
||||
} /* namespace OT */
|
||||
|
||||
#endif /* OT_COLOR_SBIX_SBIX_HH */
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright © 2018 Ebrahim Byagowi
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*/
|
||||
|
||||
#ifndef OT_COLOR_SVG_SVG_HH
|
||||
#define OT_COLOR_SVG_SVG_HH
|
||||
|
||||
#include "../../../hb-open-type.hh"
|
||||
#include "../../../hb-blob.hh"
|
||||
#include "../../../hb-paint.hh"
|
||||
|
||||
/*
|
||||
* SVG -- SVG (Scalable Vector Graphics)
|
||||
* https://docs.microsoft.com/en-us/typography/opentype/spec/svg
|
||||
*/
|
||||
|
||||
#define HB_OT_TAG_SVG HB_TAG('S','V','G',' ')
|
||||
|
||||
|
||||
namespace OT {
|
||||
|
||||
|
||||
struct SVGDocumentIndexEntry
|
||||
{
|
||||
int cmp (hb_codepoint_t g) const
|
||||
{ return g < startGlyphID ? -1 : g > endGlyphID ? 1 : 0; }
|
||||
|
||||
hb_blob_t *reference_blob (hb_blob_t *svg_blob, unsigned int index_offset) const
|
||||
{
|
||||
return hb_blob_create_sub_blob (svg_blob,
|
||||
index_offset + (unsigned int) svgDoc,
|
||||
svgDocLength);
|
||||
}
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c, const void *base) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this) &&
|
||||
hb_barrier () &&
|
||||
svgDoc.sanitize (c, base, svgDocLength));
|
||||
}
|
||||
|
||||
protected:
|
||||
HBUINT16 startGlyphID; /* The first glyph ID in the range described by
|
||||
* this index entry. */
|
||||
HBUINT16 endGlyphID; /* The last glyph ID in the range described by
|
||||
* this index entry. Must be >= startGlyphID. */
|
||||
NNOffset32To<UnsizedArrayOf<HBUINT8>>
|
||||
svgDoc; /* Offset from the beginning of the SVG Document Index
|
||||
* to an SVG document. Must be non-zero. */
|
||||
HBUINT32 svgDocLength; /* Length of the SVG document.
|
||||
* Must be non-zero. */
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (12);
|
||||
};
|
||||
|
||||
struct SVG
|
||||
{
|
||||
static constexpr hb_tag_t tableTag = HB_OT_TAG_SVG;
|
||||
|
||||
bool has_data () const { return svgDocEntries; }
|
||||
|
||||
struct accelerator_t
|
||||
{
|
||||
accelerator_t (hb_face_t *face)
|
||||
{ table = hb_sanitize_context_t ().reference_table<SVG> (face); }
|
||||
~accelerator_t () { table.destroy (); }
|
||||
|
||||
hb_blob_t *reference_blob_for_glyph (hb_codepoint_t glyph_id) const
|
||||
{
|
||||
return table->get_glyph_entry (glyph_id).reference_blob (table.get_blob (),
|
||||
table->svgDocEntries);
|
||||
}
|
||||
|
||||
bool has_data () const { return table->has_data (); }
|
||||
|
||||
bool paint_glyph (hb_font_t *font HB_UNUSED, hb_codepoint_t glyph, hb_paint_funcs_t *funcs, void *data) const
|
||||
{
|
||||
if (!has_data ())
|
||||
return false;
|
||||
|
||||
hb_blob_t *blob = reference_blob_for_glyph (glyph);
|
||||
|
||||
if (blob == hb_blob_get_empty ())
|
||||
return false;
|
||||
|
||||
funcs->image (data,
|
||||
blob,
|
||||
0, 0,
|
||||
HB_PAINT_IMAGE_FORMAT_SVG,
|
||||
font->slant_xy,
|
||||
nullptr);
|
||||
|
||||
hb_blob_destroy (blob);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
hb_blob_ptr_t<SVG> table;
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (sizeof (hb_blob_ptr_t<SVG>));
|
||||
};
|
||||
|
||||
const SVGDocumentIndexEntry &get_glyph_entry (hb_codepoint_t glyph_id) const
|
||||
{ return (this+svgDocEntries).bsearch (glyph_id); }
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (likely (c->check_struct (this) &&
|
||||
(this+svgDocEntries).sanitize_shallow (c)));
|
||||
}
|
||||
|
||||
protected:
|
||||
HBUINT16 version; /* Table version (starting at 0). */
|
||||
Offset32To<SortedArray16Of<SVGDocumentIndexEntry>>
|
||||
svgDocEntries; /* Offset (relative to the start of the SVG table) to the
|
||||
* SVG Documents Index. Must be non-zero. */
|
||||
/* Array of SVG Document Index Entries. */
|
||||
HBUINT32 reserved; /* Set to 0. */
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (10);
|
||||
};
|
||||
|
||||
struct SVG_accelerator_t : SVG::accelerator_t {
|
||||
SVG_accelerator_t (hb_face_t *face) : SVG::accelerator_t (face) {}
|
||||
};
|
||||
|
||||
} /* namespace OT */
|
||||
|
||||
|
||||
#endif /* OT_COLOR_SVG_SVG_HH */
|
||||
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
* Copyright © 2007,2008,2009 Red Hat, Inc.
|
||||
* Copyright © 2010,2012 Google, Inc.
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
* Red Hat Author(s): Behdad Esfahbod
|
||||
* Google Author(s): Behdad Esfahbod, Garret Rieger
|
||||
*/
|
||||
|
||||
#ifndef OT_LAYOUT_COMMON_COVERAGE_HH
|
||||
#define OT_LAYOUT_COMMON_COVERAGE_HH
|
||||
|
||||
#include "../types.hh"
|
||||
#include "CoverageFormat1.hh"
|
||||
#include "CoverageFormat2.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace Common {
|
||||
|
||||
template<typename Iterator>
|
||||
static inline void Coverage_serialize (hb_serialize_context_t *c,
|
||||
Iterator it);
|
||||
|
||||
struct Coverage
|
||||
{
|
||||
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
CoverageFormat1_3<SmallTypes> format1;
|
||||
CoverageFormat2_4<SmallTypes> format2;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
CoverageFormat1_3<MediumTypes>format3;
|
||||
CoverageFormat2_4<MediumTypes>format4;
|
||||
#endif
|
||||
} u;
|
||||
public:
|
||||
DEFINE_SIZE_UNION (2, format);
|
||||
|
||||
#ifndef HB_OPTIMIZE_SIZE
|
||||
HB_ALWAYS_INLINE
|
||||
#endif
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
if (!u.format.sanitize (c)) return_trace (false);
|
||||
hb_barrier ();
|
||||
switch (u.format)
|
||||
{
|
||||
case 1: return_trace (u.format1.sanitize (c));
|
||||
case 2: return_trace (u.format2.sanitize (c));
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return_trace (u.format3.sanitize (c));
|
||||
case 4: return_trace (u.format4.sanitize (c));
|
||||
#endif
|
||||
default:return_trace (true);
|
||||
}
|
||||
}
|
||||
|
||||
/* Has interface. */
|
||||
unsigned operator [] (hb_codepoint_t k) const { return get (k); }
|
||||
bool has (hb_codepoint_t k) const { return (*this)[k] != NOT_COVERED; }
|
||||
/* Predicate. */
|
||||
bool operator () (hb_codepoint_t k) const { return has (k); }
|
||||
|
||||
unsigned int get (hb_codepoint_t k) const { return get_coverage (k); }
|
||||
unsigned int get_coverage (hb_codepoint_t glyph_id) const
|
||||
{
|
||||
switch (u.format) {
|
||||
case 1: return u.format1.get_coverage (glyph_id);
|
||||
case 2: return u.format2.get_coverage (glyph_id);
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return u.format3.get_coverage (glyph_id);
|
||||
case 4: return u.format4.get_coverage (glyph_id);
|
||||
#endif
|
||||
default:return NOT_COVERED;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned get_population () const
|
||||
{
|
||||
switch (u.format) {
|
||||
case 1: return u.format1.get_population ();
|
||||
case 2: return u.format2.get_population ();
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return u.format3.get_population ();
|
||||
case 4: return u.format4.get_population ();
|
||||
#endif
|
||||
default:return NOT_COVERED;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator,
|
||||
hb_requires (hb_is_sorted_source_of (Iterator, hb_codepoint_t))>
|
||||
bool serialize (hb_serialize_context_t *c, Iterator glyphs)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!c->extend_min (this))) return_trace (false);
|
||||
|
||||
unsigned count = hb_len (glyphs);
|
||||
unsigned num_ranges = 0;
|
||||
hb_codepoint_t last = (hb_codepoint_t) -2;
|
||||
hb_codepoint_t max = 0;
|
||||
bool unsorted = false;
|
||||
for (auto g: glyphs)
|
||||
{
|
||||
if (last != (hb_codepoint_t) -2 && g < last)
|
||||
unsorted = true;
|
||||
if (last + 1 != g)
|
||||
num_ranges++;
|
||||
last = g;
|
||||
if (g > max) max = g;
|
||||
}
|
||||
u.format = !unsorted && count <= num_ranges * 3 ? 1 : 2;
|
||||
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
if (max > 0xFFFFu)
|
||||
u.format += 2;
|
||||
if (unlikely (max > 0xFFFFFFu))
|
||||
#else
|
||||
if (unlikely (max > 0xFFFFu))
|
||||
#endif
|
||||
{
|
||||
c->check_success (false, HB_SERIALIZE_ERROR_INT_OVERFLOW);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
switch (u.format)
|
||||
{
|
||||
case 1: return_trace (u.format1.serialize (c, glyphs));
|
||||
case 2: return_trace (u.format2.serialize (c, glyphs));
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return_trace (u.format3.serialize (c, glyphs));
|
||||
case 4: return_trace (u.format4.serialize (c, glyphs));
|
||||
#endif
|
||||
default:return_trace (false);
|
||||
}
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
auto it =
|
||||
+ iter ()
|
||||
| hb_take (c->plan->source->get_num_glyphs ())
|
||||
| hb_map_retains_sorting (c->plan->glyph_map_gsub)
|
||||
| hb_filter ([] (hb_codepoint_t glyph) { return glyph != HB_MAP_VALUE_INVALID; })
|
||||
;
|
||||
|
||||
// Cache the iterator result as it will be iterated multiple times
|
||||
// by the serialize code below.
|
||||
hb_sorted_vector_t<hb_codepoint_t> glyphs (it);
|
||||
Coverage_serialize (c->serializer, glyphs.iter ());
|
||||
return_trace (bool (glyphs));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
switch (u.format)
|
||||
{
|
||||
case 1: return u.format1.intersects (glyphs);
|
||||
case 2: return u.format2.intersects (glyphs);
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return u.format3.intersects (glyphs);
|
||||
case 4: return u.format4.intersects (glyphs);
|
||||
#endif
|
||||
default:return false;
|
||||
}
|
||||
}
|
||||
bool intersects_coverage (const hb_set_t *glyphs, unsigned int index) const
|
||||
{
|
||||
switch (u.format)
|
||||
{
|
||||
case 1: return u.format1.intersects_coverage (glyphs, index);
|
||||
case 2: return u.format2.intersects_coverage (glyphs, index);
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return u.format3.intersects_coverage (glyphs, index);
|
||||
case 4: return u.format4.intersects_coverage (glyphs, index);
|
||||
#endif
|
||||
default:return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Might return false if array looks unsorted.
|
||||
* Used for faster rejection of corrupt data. */
|
||||
template <typename set_t>
|
||||
bool collect_coverage (set_t *glyphs) const
|
||||
{
|
||||
switch (u.format)
|
||||
{
|
||||
case 1: return u.format1.collect_coverage (glyphs);
|
||||
case 2: return u.format2.collect_coverage (glyphs);
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return u.format3.collect_coverage (glyphs);
|
||||
case 4: return u.format4.collect_coverage (glyphs);
|
||||
#endif
|
||||
default:return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename IterableOut,
|
||||
hb_requires (hb_is_sink_of (IterableOut, hb_codepoint_t))>
|
||||
void intersect_set (const hb_set_t &glyphs, IterableOut&& intersect_glyphs) const
|
||||
{
|
||||
switch (u.format)
|
||||
{
|
||||
case 1: return u.format1.intersect_set (glyphs, intersect_glyphs);
|
||||
case 2: return u.format2.intersect_set (glyphs, intersect_glyphs);
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return u.format3.intersect_set (glyphs, intersect_glyphs);
|
||||
case 4: return u.format4.intersect_set (glyphs, intersect_glyphs);
|
||||
#endif
|
||||
default:return ;
|
||||
}
|
||||
}
|
||||
|
||||
struct iter_t : hb_iter_with_fallback_t<iter_t, hb_codepoint_t>
|
||||
{
|
||||
static constexpr bool is_sorted_iterator = true;
|
||||
iter_t (const Coverage &c_ = Null (Coverage))
|
||||
{
|
||||
hb_memset (this, 0, sizeof (*this));
|
||||
format = c_.u.format;
|
||||
switch (format)
|
||||
{
|
||||
case 1: u.format1.init (c_.u.format1); return;
|
||||
case 2: u.format2.init (c_.u.format2); return;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: u.format3.init (c_.u.format3); return;
|
||||
case 4: u.format4.init (c_.u.format4); return;
|
||||
#endif
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
bool __more__ () const
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case 1: return u.format1.__more__ ();
|
||||
case 2: return u.format2.__more__ ();
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return u.format3.__more__ ();
|
||||
case 4: return u.format4.__more__ ();
|
||||
#endif
|
||||
default:return false;
|
||||
}
|
||||
}
|
||||
void __next__ ()
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case 1: u.format1.__next__ (); break;
|
||||
case 2: u.format2.__next__ (); break;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: u.format3.__next__ (); break;
|
||||
case 4: u.format4.__next__ (); break;
|
||||
#endif
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
typedef hb_codepoint_t __item_t__;
|
||||
__item_t__ __item__ () const { return get_glyph (); }
|
||||
|
||||
hb_codepoint_t get_glyph () const
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case 1: return u.format1.get_glyph ();
|
||||
case 2: return u.format2.get_glyph ();
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return u.format3.get_glyph ();
|
||||
case 4: return u.format4.get_glyph ();
|
||||
#endif
|
||||
default:return 0;
|
||||
}
|
||||
}
|
||||
bool operator != (const iter_t& o) const
|
||||
{
|
||||
if (unlikely (format != o.format)) return true;
|
||||
switch (format)
|
||||
{
|
||||
case 1: return u.format1 != o.u.format1;
|
||||
case 2: return u.format2 != o.u.format2;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return u.format3 != o.u.format3;
|
||||
case 4: return u.format4 != o.u.format4;
|
||||
#endif
|
||||
default:return false;
|
||||
}
|
||||
}
|
||||
iter_t __end__ () const
|
||||
{
|
||||
iter_t it = {};
|
||||
it.format = format;
|
||||
switch (format)
|
||||
{
|
||||
case 1: it.u.format1 = u.format1.__end__ (); break;
|
||||
case 2: it.u.format2 = u.format2.__end__ (); break;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: it.u.format3 = u.format3.__end__ (); break;
|
||||
case 4: it.u.format4 = u.format4.__end__ (); break;
|
||||
#endif
|
||||
default: break;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned int format;
|
||||
union {
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
CoverageFormat2_4<MediumTypes>::iter_t format4; /* Put this one first since it's larger; helps shut up compiler. */
|
||||
CoverageFormat1_3<MediumTypes>::iter_t format3;
|
||||
#endif
|
||||
CoverageFormat2_4<SmallTypes>::iter_t format2; /* Put this one first since it's larger; helps shut up compiler. */
|
||||
CoverageFormat1_3<SmallTypes>::iter_t format1;
|
||||
} u;
|
||||
};
|
||||
iter_t iter () const { return iter_t (*this); }
|
||||
};
|
||||
|
||||
template<typename Iterator>
|
||||
static inline void
|
||||
Coverage_serialize (hb_serialize_context_t *c,
|
||||
Iterator it)
|
||||
{ c->start_embed<Coverage> ()->serialize (c, it); }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // #ifndef OT_LAYOUT_COMMON_COVERAGE_HH
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright © 2007,2008,2009 Red Hat, Inc.
|
||||
* Copyright © 2010,2012 Google, Inc.
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
* Red Hat Author(s): Behdad Esfahbod
|
||||
* Google Author(s): Behdad Esfahbod, Garret Rieger
|
||||
*/
|
||||
|
||||
|
||||
#ifndef OT_LAYOUT_COMMON_COVERAGEFORMAT1_HH
|
||||
#define OT_LAYOUT_COMMON_COVERAGEFORMAT1_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace Common {
|
||||
|
||||
#define NOT_COVERED ((unsigned int) -1)
|
||||
|
||||
template <typename Types>
|
||||
struct CoverageFormat1_3
|
||||
{
|
||||
friend struct Coverage;
|
||||
|
||||
protected:
|
||||
HBUINT16 coverageFormat; /* Format identifier--format = 1 */
|
||||
SortedArray16Of<typename Types::HBGlyphID>
|
||||
glyphArray; /* Array of GlyphIDs--in numerical order */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (4, glyphArray);
|
||||
|
||||
private:
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (glyphArray.sanitize (c));
|
||||
}
|
||||
|
||||
unsigned int get_coverage (hb_codepoint_t glyph_id) const
|
||||
{
|
||||
unsigned int i;
|
||||
glyphArray.bfind (glyph_id, &i, HB_NOT_FOUND_STORE, NOT_COVERED);
|
||||
return i;
|
||||
}
|
||||
|
||||
unsigned get_population () const
|
||||
{
|
||||
return glyphArray.len;
|
||||
}
|
||||
|
||||
template <typename Iterator,
|
||||
hb_requires (hb_is_sorted_source_of (Iterator, hb_codepoint_t))>
|
||||
bool serialize (hb_serialize_context_t *c, Iterator glyphs)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
return_trace (glyphArray.serialize (c, glyphs));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
if (glyphArray.len > glyphs->get_population () * hb_bit_storage ((unsigned) glyphArray.len) / 2)
|
||||
{
|
||||
for (auto g : *glyphs)
|
||||
if (get_coverage (g) != NOT_COVERED)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& g : glyphArray.as_array ())
|
||||
if (glyphs->has (g))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool intersects_coverage (const hb_set_t *glyphs, unsigned int index) const
|
||||
{ return glyphs->has (glyphArray[index]); }
|
||||
|
||||
template <typename IterableOut,
|
||||
hb_requires (hb_is_sink_of (IterableOut, hb_codepoint_t))>
|
||||
void intersect_set (const hb_set_t &glyphs, IterableOut&& intersect_glyphs) const
|
||||
{
|
||||
unsigned count = glyphArray.len;
|
||||
for (unsigned i = 0; i < count; i++)
|
||||
if (glyphs.has (glyphArray[i]))
|
||||
intersect_glyphs << glyphArray[i];
|
||||
}
|
||||
|
||||
template <typename set_t>
|
||||
bool collect_coverage (set_t *glyphs) const
|
||||
{ return glyphs->add_sorted_array (glyphArray.as_array ()); }
|
||||
|
||||
public:
|
||||
/* Older compilers need this to be public. */
|
||||
struct iter_t
|
||||
{
|
||||
void init (const struct CoverageFormat1_3 &c_) { c = &c_; i = 0; }
|
||||
bool __more__ () const { return i < c->glyphArray.len; }
|
||||
void __next__ () { i++; }
|
||||
hb_codepoint_t get_glyph () const { return c->glyphArray[i]; }
|
||||
bool operator != (const iter_t& o) const
|
||||
{ return i != o.i; }
|
||||
iter_t __end__ () const { iter_t it; it.init (*c); it.i = c->glyphArray.len; return it; }
|
||||
|
||||
private:
|
||||
const struct CoverageFormat1_3 *c;
|
||||
unsigned int i;
|
||||
};
|
||||
private:
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // #ifndef OT_LAYOUT_COMMON_COVERAGEFORMAT1_HH
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright © 2007,2008,2009 Red Hat, Inc.
|
||||
* Copyright © 2010,2012 Google, Inc.
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
* Red Hat Author(s): Behdad Esfahbod
|
||||
* Google Author(s): Behdad Esfahbod, Garret Rieger
|
||||
*/
|
||||
|
||||
#ifndef OT_LAYOUT_COMMON_COVERAGEFORMAT2_HH
|
||||
#define OT_LAYOUT_COMMON_COVERAGEFORMAT2_HH
|
||||
|
||||
#include "RangeRecord.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace Common {
|
||||
|
||||
template <typename Types>
|
||||
struct CoverageFormat2_4
|
||||
{
|
||||
friend struct Coverage;
|
||||
|
||||
protected:
|
||||
HBUINT16 coverageFormat; /* Format identifier--format = 2 */
|
||||
SortedArray16Of<RangeRecord<Types>>
|
||||
rangeRecord; /* Array of glyph ranges--ordered by
|
||||
* Start GlyphID. rangeCount entries
|
||||
* long */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (4, rangeRecord);
|
||||
|
||||
private:
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (rangeRecord.sanitize (c));
|
||||
}
|
||||
|
||||
unsigned int get_coverage (hb_codepoint_t glyph_id) const
|
||||
{
|
||||
const RangeRecord<Types> &range = rangeRecord.bsearch (glyph_id);
|
||||
return likely (range.first <= range.last)
|
||||
? (unsigned int) range.value + (glyph_id - range.first)
|
||||
: NOT_COVERED;
|
||||
}
|
||||
|
||||
unsigned get_population () const
|
||||
{
|
||||
typename Types::large_int ret = 0;
|
||||
for (const auto &r : rangeRecord)
|
||||
ret += r.get_population ();
|
||||
return ret > UINT_MAX ? UINT_MAX : (unsigned) ret;
|
||||
}
|
||||
|
||||
template <typename Iterator,
|
||||
hb_requires (hb_is_sorted_source_of (Iterator, hb_codepoint_t))>
|
||||
bool serialize (hb_serialize_context_t *c, Iterator glyphs)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!c->extend_min (this))) return_trace (false);
|
||||
|
||||
unsigned num_ranges = 0;
|
||||
hb_codepoint_t last = (hb_codepoint_t) -2;
|
||||
for (auto g: glyphs)
|
||||
{
|
||||
if (last + 1 != g)
|
||||
num_ranges++;
|
||||
last = g;
|
||||
}
|
||||
|
||||
if (unlikely (!rangeRecord.serialize (c, num_ranges))) return_trace (false);
|
||||
if (!num_ranges) return_trace (true);
|
||||
|
||||
unsigned count = 0;
|
||||
unsigned range = (unsigned) -1;
|
||||
last = (hb_codepoint_t) -2;
|
||||
unsigned unsorted = false;
|
||||
for (auto g: glyphs)
|
||||
{
|
||||
if (last + 1 != g)
|
||||
{
|
||||
if (unlikely (last != (hb_codepoint_t) -2 && last + 1 > g))
|
||||
unsorted = true;
|
||||
|
||||
range++;
|
||||
rangeRecord.arrayZ[range].first = g;
|
||||
rangeRecord.arrayZ[range].value = count;
|
||||
}
|
||||
rangeRecord.arrayZ[range].last = g;
|
||||
last = g;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (unlikely (unsorted))
|
||||
rangeRecord.as_array ().qsort (RangeRecord<Types>::cmp_range);
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
if (rangeRecord.len > glyphs->get_population () * hb_bit_storage ((unsigned) rangeRecord.len) / 2)
|
||||
{
|
||||
for (auto g : *glyphs)
|
||||
if (get_coverage (g) != NOT_COVERED)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return hb_any (+ hb_iter (rangeRecord)
|
||||
| hb_map ([glyphs] (const RangeRecord<Types> &range) { return range.intersects (*glyphs); }));
|
||||
}
|
||||
bool intersects_coverage (const hb_set_t *glyphs, unsigned int index) const
|
||||
{
|
||||
auto *range = rangeRecord.as_array ().bsearch (index);
|
||||
if (range)
|
||||
return range->intersects (*glyphs);
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename IterableOut,
|
||||
hb_requires (hb_is_sink_of (IterableOut, hb_codepoint_t))>
|
||||
void intersect_set (const hb_set_t &glyphs, IterableOut&& intersect_glyphs) const
|
||||
{
|
||||
/* Break out of loop for overlapping, broken, tables,
|
||||
* to avoid fuzzer timouts. */
|
||||
hb_codepoint_t last = 0;
|
||||
for (const auto& range : rangeRecord)
|
||||
{
|
||||
if (unlikely (range.first < last))
|
||||
break;
|
||||
last = range.last;
|
||||
for (hb_codepoint_t g = range.first - 1;
|
||||
glyphs.next (&g) && g <= last;)
|
||||
intersect_glyphs << g;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename set_t>
|
||||
bool collect_coverage (set_t *glyphs) const
|
||||
{
|
||||
for (const auto& range: rangeRecord)
|
||||
if (unlikely (!range.collect_coverage (glyphs)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
/* Older compilers need this to be public. */
|
||||
struct iter_t
|
||||
{
|
||||
void init (const CoverageFormat2_4 &c_)
|
||||
{
|
||||
c = &c_;
|
||||
coverage = 0;
|
||||
i = 0;
|
||||
j = c->rangeRecord.len ? c->rangeRecord[0].first : 0;
|
||||
if (unlikely (c->rangeRecord[0].first > c->rangeRecord[0].last))
|
||||
{
|
||||
/* Broken table. Skip. */
|
||||
i = c->rangeRecord.len;
|
||||
j = 0;
|
||||
}
|
||||
}
|
||||
bool __more__ () const { return i < c->rangeRecord.len; }
|
||||
void __next__ ()
|
||||
{
|
||||
if (j >= c->rangeRecord[i].last)
|
||||
{
|
||||
i++;
|
||||
if (__more__ ())
|
||||
{
|
||||
unsigned int old = coverage;
|
||||
j = c->rangeRecord.arrayZ[i].first;
|
||||
coverage = c->rangeRecord.arrayZ[i].value;
|
||||
if (unlikely (coverage != old + 1))
|
||||
{
|
||||
/* Broken table. Skip. Important to avoid DoS.
|
||||
* Also, our callers depend on coverage being
|
||||
* consecutive and monotonically increasing,
|
||||
* ie. iota(). */
|
||||
i = c->rangeRecord.len;
|
||||
j = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
j = 0;
|
||||
return;
|
||||
}
|
||||
coverage++;
|
||||
j++;
|
||||
}
|
||||
hb_codepoint_t get_glyph () const { return j; }
|
||||
bool operator != (const iter_t& o) const
|
||||
{ return i != o.i || j != o.j; }
|
||||
iter_t __end__ () const
|
||||
{
|
||||
iter_t it;
|
||||
it.init (*c);
|
||||
it.i = c->rangeRecord.len;
|
||||
it.j = 0;
|
||||
return it;
|
||||
}
|
||||
|
||||
private:
|
||||
const struct CoverageFormat2_4 *c;
|
||||
unsigned int i, coverage;
|
||||
hb_codepoint_t j;
|
||||
};
|
||||
private:
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // #ifndef OT_LAYOUT_COMMON_COVERAGEFORMAT2_HH
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright © 2007,2008,2009 Red Hat, Inc.
|
||||
* Copyright © 2010,2012 Google, Inc.
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
* Red Hat Author(s): Behdad Esfahbod
|
||||
* Google Author(s): Behdad Esfahbod, Garret Rieger
|
||||
*/
|
||||
|
||||
#ifndef OT_LAYOUT_COMMON_RANGERECORD_HH
|
||||
#define OT_LAYOUT_COMMON_RANGERECORD_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace Common {
|
||||
|
||||
template <typename Types>
|
||||
struct RangeRecord
|
||||
{
|
||||
typename Types::HBGlyphID first; /* First GlyphID in the range */
|
||||
typename Types::HBGlyphID last; /* Last GlyphID in the range */
|
||||
HBUINT16 value; /* Value */
|
||||
|
||||
DEFINE_SIZE_STATIC (2 + 2 * Types::size);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this));
|
||||
}
|
||||
|
||||
int cmp (hb_codepoint_t g) const
|
||||
{ return g < first ? -1 : g <= last ? 0 : +1; }
|
||||
|
||||
HB_INTERNAL static int cmp_range (const void *pa, const void *pb) {
|
||||
const RangeRecord *a = (const RangeRecord *) pa;
|
||||
const RangeRecord *b = (const RangeRecord *) pb;
|
||||
if (a->first < b->first) return -1;
|
||||
if (a->first > b->first) return +1;
|
||||
if (a->last < b->last) return -1;
|
||||
if (a->last > b->last) return +1;
|
||||
if (a->value < b->value) return -1;
|
||||
if (a->value > b->value) return +1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned get_population () const
|
||||
{
|
||||
if (unlikely (last < first)) return 0;
|
||||
return (last - first + 1);
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t &glyphs) const
|
||||
{ return glyphs.intersects (first, last); }
|
||||
|
||||
template <typename set_t>
|
||||
bool collect_coverage (set_t *glyphs) const
|
||||
{ return glyphs->add_range (first, last); }
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(garretrieger): This was previously implemented using
|
||||
// DECLARE_NULL_NAMESPACE_BYTES_TEMPLATE1 (OT, RangeRecord, 9);
|
||||
// but that only works when there is only a single namespace level.
|
||||
// The macro should probably be fixed so it can work in this situation.
|
||||
extern HB_INTERNAL const unsigned char _hb_Null_OT_RangeRecord[9];
|
||||
template <typename Spec>
|
||||
struct Null<OT::Layout::Common::RangeRecord<Spec>> {
|
||||
static OT::Layout::Common::RangeRecord<Spec> const & get_null () {
|
||||
return *reinterpret_cast<const OT::Layout::Common::RangeRecord<Spec> *> (_hb_Null_OT_RangeRecord);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif // #ifndef OT_LAYOUT_COMMON_RANGERECORD_HH
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
#ifndef OT_LAYOUT_GPOS_ANCHOR_HH
|
||||
#define OT_LAYOUT_GPOS_ANCHOR_HH
|
||||
|
||||
#include "AnchorFormat1.hh"
|
||||
#include "AnchorFormat2.hh"
|
||||
#include "AnchorFormat3.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct Anchor
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
AnchorFormat1 format1;
|
||||
AnchorFormat2 format2;
|
||||
AnchorFormat3 format3;
|
||||
} u;
|
||||
public:
|
||||
DEFINE_SIZE_UNION (2, format);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
if (!u.format.sanitize (c)) return_trace (false);
|
||||
hb_barrier ();
|
||||
switch (u.format) {
|
||||
case 1: return_trace (u.format1.sanitize (c));
|
||||
case 2: return_trace (u.format2.sanitize (c));
|
||||
case 3: return_trace (u.format3.sanitize (c));
|
||||
default:return_trace (true);
|
||||
}
|
||||
}
|
||||
|
||||
void get_anchor (hb_ot_apply_context_t *c, hb_codepoint_t glyph_id,
|
||||
float *x, float *y) const
|
||||
{
|
||||
*x = *y = 0;
|
||||
switch (u.format) {
|
||||
case 1: u.format1.get_anchor (c, glyph_id, x, y); return;
|
||||
case 2: u.format2.get_anchor (c, glyph_id, x, y); return;
|
||||
case 3: u.format3.get_anchor (c, glyph_id, x, y); return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (bool (reinterpret_cast<Anchor *> (u.format1.copy (c->serializer))));
|
||||
case 2:
|
||||
if (c->plan->flags & HB_SUBSET_FLAGS_NO_HINTING)
|
||||
{
|
||||
// AnchorFormat 2 just containins extra hinting information, so
|
||||
// if hints are being dropped convert to format 1.
|
||||
return_trace (bool (reinterpret_cast<Anchor *> (u.format1.copy (c->serializer))));
|
||||
}
|
||||
return_trace (bool (reinterpret_cast<Anchor *> (u.format2.copy (c->serializer))));
|
||||
case 3: return_trace (u.format3.subset (c));
|
||||
default:return_trace (false);
|
||||
}
|
||||
}
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
|
||||
{
|
||||
switch (u.format) {
|
||||
case 1: case 2:
|
||||
return;
|
||||
case 3:
|
||||
u.format3.collect_variation_indices (c);
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OT_LAYOUT_GPOS_ANCHOR_HH
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
#ifndef OT_LAYOUT_GPOS_ANCHORFORMAT1_HH
|
||||
#define OT_LAYOUT_GPOS_ANCHORFORMAT1_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct AnchorFormat1
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
FWORD xCoordinate; /* Horizontal value--in design units */
|
||||
FWORD yCoordinate; /* Vertical value--in design units */
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (6);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this));
|
||||
}
|
||||
|
||||
void get_anchor (hb_ot_apply_context_t *c, hb_codepoint_t glyph_id HB_UNUSED,
|
||||
float *x, float *y) const
|
||||
{
|
||||
hb_font_t *font = c->font;
|
||||
*x = font->em_fscale_x (xCoordinate);
|
||||
*y = font->em_fscale_y (yCoordinate);
|
||||
}
|
||||
|
||||
AnchorFormat1* copy (hb_serialize_context_t *c) const
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
AnchorFormat1* out = c->embed<AnchorFormat1> (this);
|
||||
if (!out) return_trace (out);
|
||||
out->format = 1;
|
||||
return_trace (out);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OT_LAYOUT_GPOS_ANCHORFORMAT1_HH
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
#ifndef OT_LAYOUT_GPOS_ANCHORFORMAT2_HH
|
||||
#define OT_LAYOUT_GPOS_ANCHORFORMAT2_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct AnchorFormat2
|
||||
{
|
||||
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 2 */
|
||||
FWORD xCoordinate; /* Horizontal value--in design units */
|
||||
FWORD yCoordinate; /* Vertical value--in design units */
|
||||
HBUINT16 anchorPoint; /* Index to glyph contour point */
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (8);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this));
|
||||
}
|
||||
|
||||
void get_anchor (hb_ot_apply_context_t *c, hb_codepoint_t glyph_id,
|
||||
float *x, float *y) const
|
||||
{
|
||||
hb_font_t *font = c->font;
|
||||
|
||||
#ifdef HB_NO_HINTING
|
||||
*x = font->em_fscale_x (xCoordinate);
|
||||
*y = font->em_fscale_y (yCoordinate);
|
||||
return;
|
||||
#endif
|
||||
|
||||
unsigned int x_ppem = font->x_ppem;
|
||||
unsigned int y_ppem = font->y_ppem;
|
||||
hb_position_t cx = 0, cy = 0;
|
||||
bool ret;
|
||||
|
||||
ret = (x_ppem || y_ppem) &&
|
||||
font->get_glyph_contour_point_for_origin (glyph_id, anchorPoint, HB_DIRECTION_LTR, &cx, &cy);
|
||||
*x = ret && x_ppem ? cx : font->em_fscale_x (xCoordinate);
|
||||
*y = ret && y_ppem ? cy : font->em_fscale_y (yCoordinate);
|
||||
}
|
||||
|
||||
AnchorFormat2* copy (hb_serialize_context_t *c) const
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
return_trace (c->embed<AnchorFormat2> (this));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OT_LAYOUT_GPOS_ANCHORFORMAT2_HH
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
#ifndef OT_LAYOUT_GPOS_ANCHORFORMAT3_HH
|
||||
#define OT_LAYOUT_GPOS_ANCHORFORMAT3_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct AnchorFormat3
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 3 */
|
||||
FWORD xCoordinate; /* Horizontal value--in design units */
|
||||
FWORD yCoordinate; /* Vertical value--in design units */
|
||||
Offset16To<Device>
|
||||
xDeviceTable; /* Offset to Device table for X
|
||||
* coordinate-- from beginning of
|
||||
* Anchor table (may be NULL) */
|
||||
Offset16To<Device>
|
||||
yDeviceTable; /* Offset to Device table for Y
|
||||
* coordinate-- from beginning of
|
||||
* Anchor table (may be NULL) */
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (10);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
if (unlikely (!c->check_struct (this))) return_trace (false);
|
||||
|
||||
return_trace (xDeviceTable.sanitize (c, this) && yDeviceTable.sanitize (c, this));
|
||||
}
|
||||
|
||||
void get_anchor (hb_ot_apply_context_t *c, hb_codepoint_t glyph_id HB_UNUSED,
|
||||
float *x, float *y) const
|
||||
{
|
||||
hb_font_t *font = c->font;
|
||||
*x = font->em_fscale_x (xCoordinate);
|
||||
*y = font->em_fscale_y (yCoordinate);
|
||||
|
||||
if ((font->x_ppem || font->num_coords) && xDeviceTable.sanitize (&c->sanitizer, this))
|
||||
{
|
||||
hb_barrier ();
|
||||
*x += (this+xDeviceTable).get_x_delta (font, c->var_store, c->var_store_cache);
|
||||
}
|
||||
if ((font->y_ppem || font->num_coords) && yDeviceTable.sanitize (&c->sanitizer, this))
|
||||
{
|
||||
hb_barrier ();
|
||||
*y += (this+yDeviceTable).get_y_delta (font, c->var_store, c->var_store_cache);
|
||||
}
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->embed (format))) return_trace (false);
|
||||
if (unlikely (!c->serializer->embed (xCoordinate))) return_trace (false);
|
||||
if (unlikely (!c->serializer->embed (yCoordinate))) return_trace (false);
|
||||
|
||||
unsigned x_varidx = xDeviceTable ? (this+xDeviceTable).get_variation_index () : HB_OT_LAYOUT_NO_VARIATIONS_INDEX;
|
||||
if (x_varidx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
|
||||
{
|
||||
hb_pair_t<unsigned, int> *new_varidx_delta;
|
||||
if (!c->plan->layout_variation_idx_delta_map.has (x_varidx, &new_varidx_delta))
|
||||
return_trace (false);
|
||||
|
||||
x_varidx = hb_first (*new_varidx_delta);
|
||||
int delta = hb_second (*new_varidx_delta);
|
||||
if (delta != 0)
|
||||
{
|
||||
if (!c->serializer->check_assign (out->xCoordinate, xCoordinate + delta,
|
||||
HB_SERIALIZE_ERROR_INT_OVERFLOW))
|
||||
return_trace (false);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned y_varidx = yDeviceTable ? (this+yDeviceTable).get_variation_index () : HB_OT_LAYOUT_NO_VARIATIONS_INDEX;
|
||||
if (y_varidx != HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
|
||||
{
|
||||
hb_pair_t<unsigned, int> *new_varidx_delta;
|
||||
if (!c->plan->layout_variation_idx_delta_map.has (y_varidx, &new_varidx_delta))
|
||||
return_trace (false);
|
||||
|
||||
y_varidx = hb_first (*new_varidx_delta);
|
||||
int delta = hb_second (*new_varidx_delta);
|
||||
if (delta != 0)
|
||||
{
|
||||
if (!c->serializer->check_assign (out->yCoordinate, yCoordinate + delta,
|
||||
HB_SERIALIZE_ERROR_INT_OVERFLOW))
|
||||
return_trace (false);
|
||||
}
|
||||
}
|
||||
|
||||
/* in case that all axes are pinned or no variations after instantiation,
|
||||
* both var_idxes will be mapped to HB_OT_LAYOUT_NO_VARIATIONS_INDEX */
|
||||
if (x_varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX &&
|
||||
y_varidx == HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
|
||||
return_trace (c->serializer->check_assign (out->format, 1, HB_SERIALIZE_ERROR_INT_OVERFLOW));
|
||||
|
||||
if (!c->serializer->embed (xDeviceTable)) return_trace (false);
|
||||
if (!c->serializer->embed (yDeviceTable)) return_trace (false);
|
||||
|
||||
out->xDeviceTable.serialize_copy (c->serializer, xDeviceTable, this, 0, hb_serialize_context_t::Head, &c->plan->layout_variation_idx_delta_map);
|
||||
out->yDeviceTable.serialize_copy (c->serializer, yDeviceTable, this, 0, hb_serialize_context_t::Head, &c->plan->layout_variation_idx_delta_map);
|
||||
return_trace (out);
|
||||
}
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
|
||||
{
|
||||
(this+xDeviceTable).collect_variation_indices (c);
|
||||
(this+yDeviceTable).collect_variation_indices (c);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OT_LAYOUT_GPOS_ANCHORFORMAT3_HH
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
#ifndef OT_LAYOUT_GPOS_ANCHORMATRIX_HH
|
||||
#define OT_LAYOUT_GPOS_ANCHORMATRIX_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct AnchorMatrix
|
||||
{
|
||||
HBUINT16 rows; /* Number of rows */
|
||||
UnsizedArrayOf<Offset16To<Anchor, AnchorMatrix>>
|
||||
matrixZ; /* Matrix of offsets to Anchor tables--
|
||||
* from beginning of AnchorMatrix table */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (2, matrixZ);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c, unsigned int cols) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
if (!c->check_struct (this)) return_trace (false);
|
||||
hb_barrier ();
|
||||
if (unlikely (hb_unsigned_mul_overflows (rows, cols))) return_trace (false);
|
||||
unsigned int count = rows * cols;
|
||||
if (!c->check_array (matrixZ.arrayZ, count)) return_trace (false);
|
||||
|
||||
if (c->lazy_some_gpos)
|
||||
return_trace (true);
|
||||
|
||||
hb_barrier ();
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
if (!matrixZ[i].sanitize (c, this)) return_trace (false);
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
const Anchor& get_anchor (hb_ot_apply_context_t *c,
|
||||
unsigned int row, unsigned int col,
|
||||
unsigned int cols, bool *found) const
|
||||
{
|
||||
*found = false;
|
||||
if (unlikely (row >= rows || col >= cols)) return Null (Anchor);
|
||||
auto &offset = matrixZ[row * cols + col];
|
||||
if (unlikely (!offset.sanitize (&c->sanitizer, this))) return Null (Anchor);
|
||||
hb_barrier ();
|
||||
*found = !offset.is_null ();
|
||||
return this+offset;
|
||||
}
|
||||
|
||||
template <typename Iterator,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
|
||||
Iterator index_iter) const
|
||||
{
|
||||
for (unsigned i : index_iter)
|
||||
(this+matrixZ[i]).collect_variation_indices (c);
|
||||
}
|
||||
|
||||
template <typename Iterator,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
bool subset (hb_subset_context_t *c,
|
||||
unsigned num_rows,
|
||||
Iterator index_iter) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
|
||||
auto *out = c->serializer->start_embed (this);
|
||||
|
||||
if (!index_iter) return_trace (false);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
|
||||
out->rows = num_rows;
|
||||
for (const unsigned i : index_iter)
|
||||
{
|
||||
auto *offset = c->serializer->embed (matrixZ[i]);
|
||||
if (!offset) return_trace (false);
|
||||
offset->serialize_subset (c, matrixZ[i], this);
|
||||
}
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_ANCHORMATRIX_HH */
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
#ifndef OT_LAYOUT_GPOS_CHAINCONTEXTPOS_HH
|
||||
#define OT_LAYOUT_GPOS_CHAINCONTEXTPOS_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct ChainContextPos : ChainContext {};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_CHAINCONTEXTPOS_HH */
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef OT_LAYOUT_GPOS_COMMON_HH
|
||||
#define OT_LAYOUT_GPOS_COMMON_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
enum attach_type_t {
|
||||
ATTACH_TYPE_NONE = 0X00,
|
||||
|
||||
/* Each attachment should be either a mark or a cursive; can't be both. */
|
||||
ATTACH_TYPE_MARK = 0X01,
|
||||
ATTACH_TYPE_CURSIVE = 0X02,
|
||||
};
|
||||
|
||||
/* buffer **position** var allocations */
|
||||
#define attach_chain() var.i16[0] /* glyph to which this attaches to, relative to current glyphs; negative for going back, positive for forward. */
|
||||
#define attach_type() var.u8[2] /* attachment type */
|
||||
/* Note! if attach_chain() is zero, the value of attach_type() is irrelevant. */
|
||||
|
||||
template<typename Iterator, typename SrcLookup>
|
||||
static void SinglePos_serialize (hb_serialize_context_t *c,
|
||||
const SrcLookup *src,
|
||||
Iterator it,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map,
|
||||
unsigned new_format);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OT_LAYOUT_GPOS_COMMON_HH
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef OT_LAYOUT_GPOS_CONTEXTPOS_HH
|
||||
#define OT_LAYOUT_GPOS_CONTEXTPOS_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct ContextPos : Context {};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_CONTEXTPOS_HH */
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef OT_LAYOUT_GPOS_CURSIVEPOS_HH
|
||||
#define OT_LAYOUT_GPOS_CURSIVEPOS_HH
|
||||
|
||||
#include "CursivePosFormat1.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct CursivePos
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
CursivePosFormat1 format1;
|
||||
} u;
|
||||
|
||||
public:
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{
|
||||
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
|
||||
TRACE_DISPATCH (this, u.format);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
|
||||
default:return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_CURSIVEPOS_HH */
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
#ifndef OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH
|
||||
#define OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH
|
||||
|
||||
#include "Anchor.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct EntryExitRecord
|
||||
{
|
||||
friend struct CursivePosFormat1;
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c, const struct CursivePosFormat1 *base) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (entryAnchor.sanitize (c, base) && exitAnchor.sanitize (c, base));
|
||||
}
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
|
||||
const struct CursivePosFormat1 *src_base) const
|
||||
{
|
||||
(src_base+entryAnchor).collect_variation_indices (c);
|
||||
(src_base+exitAnchor).collect_variation_indices (c);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c,
|
||||
const struct CursivePosFormat1 *src_base) const
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
auto *out = c->serializer->embed (this);
|
||||
if (unlikely (!out)) return_trace (false);
|
||||
|
||||
bool ret = false;
|
||||
ret |= out->entryAnchor.serialize_subset (c, entryAnchor, src_base);
|
||||
ret |= out->exitAnchor.serialize_subset (c, exitAnchor, src_base);
|
||||
return_trace (ret);
|
||||
}
|
||||
|
||||
protected:
|
||||
Offset16To<Anchor, struct CursivePosFormat1>
|
||||
entryAnchor; /* Offset to EntryAnchor table--from
|
||||
* beginning of CursivePos
|
||||
* subtable--may be NULL */
|
||||
Offset16To<Anchor, struct CursivePosFormat1>
|
||||
exitAnchor; /* Offset to ExitAnchor table--from
|
||||
* beginning of CursivePos
|
||||
* subtable--may be NULL */
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (4);
|
||||
};
|
||||
|
||||
static void
|
||||
reverse_cursive_minor_offset (hb_glyph_position_t *pos, unsigned int i, hb_direction_t direction, unsigned int new_parent) {
|
||||
int chain = pos[i].attach_chain(), type = pos[i].attach_type();
|
||||
if (likely (!chain || 0 == (type & ATTACH_TYPE_CURSIVE)))
|
||||
return;
|
||||
|
||||
pos[i].attach_chain() = 0;
|
||||
|
||||
unsigned int j = (int) i + chain;
|
||||
|
||||
/* Stop if we see new parent in the chain. */
|
||||
if (j == new_parent)
|
||||
return;
|
||||
|
||||
reverse_cursive_minor_offset (pos, j, direction, new_parent);
|
||||
|
||||
if (HB_DIRECTION_IS_HORIZONTAL (direction))
|
||||
pos[j].y_offset = -pos[i].y_offset;
|
||||
else
|
||||
pos[j].x_offset = -pos[i].x_offset;
|
||||
|
||||
pos[j].attach_chain() = -chain;
|
||||
pos[j].attach_type() = type;
|
||||
}
|
||||
|
||||
|
||||
struct CursivePosFormat1
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
Offset16To<Coverage>
|
||||
coverage; /* Offset to Coverage table--from
|
||||
* beginning of subtable */
|
||||
Array16Of<EntryExitRecord>
|
||||
entryExitRecord; /* Array of EntryExit records--in
|
||||
* Coverage Index order */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (6, entryExitRecord);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
if (unlikely (!coverage.sanitize (c, this)))
|
||||
return_trace (false);
|
||||
|
||||
if (c->lazy_some_gpos)
|
||||
return_trace (entryExitRecord.sanitize_shallow (c));
|
||||
else
|
||||
return_trace (entryExitRecord.sanitize (c, this));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{ return (this+coverage).intersects (glyphs); }
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
|
||||
{
|
||||
+ hb_zip (this+coverage, entryExitRecord)
|
||||
| hb_filter (c->glyph_set, hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_apply ([&] (const EntryExitRecord& record) { record.collect_variation_indices (c, this); })
|
||||
;
|
||||
}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{ if (unlikely (!(this+coverage).collect_coverage (c->input))) return; }
|
||||
|
||||
const Coverage &get_coverage () const { return this+coverage; }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
hb_buffer_t *buffer = c->buffer;
|
||||
|
||||
const EntryExitRecord &this_record = entryExitRecord[(this+coverage).get_coverage (buffer->cur().codepoint)];
|
||||
if (!this_record.entryAnchor ||
|
||||
unlikely (!this_record.entryAnchor.sanitize (&c->sanitizer, this))) return_trace (false);
|
||||
hb_barrier ();
|
||||
|
||||
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
|
||||
skippy_iter.reset_fast (buffer->idx);
|
||||
unsigned unsafe_from;
|
||||
if (unlikely (!skippy_iter.prev (&unsafe_from)))
|
||||
{
|
||||
buffer->unsafe_to_concat_from_outbuffer (unsafe_from, buffer->idx + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
const EntryExitRecord &prev_record = entryExitRecord[(this+coverage).get_coverage (buffer->info[skippy_iter.idx].codepoint)];
|
||||
if (!prev_record.exitAnchor ||
|
||||
unlikely (!prev_record.exitAnchor.sanitize (&c->sanitizer, this)))
|
||||
{
|
||||
buffer->unsafe_to_concat_from_outbuffer (skippy_iter.idx, buffer->idx + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
hb_barrier ();
|
||||
|
||||
unsigned int i = skippy_iter.idx;
|
||||
unsigned int j = buffer->idx;
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"cursive attaching glyph at %u to glyph at %u",
|
||||
i, j);
|
||||
}
|
||||
|
||||
buffer->unsafe_to_break (i, j + 1);
|
||||
float entry_x, entry_y, exit_x, exit_y;
|
||||
(this+prev_record.exitAnchor).get_anchor (c, buffer->info[i].codepoint, &exit_x, &exit_y);
|
||||
(this+this_record.entryAnchor).get_anchor (c, buffer->info[j].codepoint, &entry_x, &entry_y);
|
||||
|
||||
hb_glyph_position_t *pos = buffer->pos;
|
||||
|
||||
hb_position_t d;
|
||||
/* Main-direction adjustment */
|
||||
switch (c->direction) {
|
||||
case HB_DIRECTION_LTR:
|
||||
pos[i].x_advance = roundf (exit_x) + pos[i].x_offset;
|
||||
|
||||
d = roundf (entry_x) + pos[j].x_offset;
|
||||
pos[j].x_advance -= d;
|
||||
pos[j].x_offset -= d;
|
||||
break;
|
||||
case HB_DIRECTION_RTL:
|
||||
d = roundf (exit_x) + pos[i].x_offset;
|
||||
pos[i].x_advance -= d;
|
||||
pos[i].x_offset -= d;
|
||||
|
||||
pos[j].x_advance = roundf (entry_x) + pos[j].x_offset;
|
||||
break;
|
||||
case HB_DIRECTION_TTB:
|
||||
pos[i].y_advance = roundf (exit_y) + pos[i].y_offset;
|
||||
|
||||
d = roundf (entry_y) + pos[j].y_offset;
|
||||
pos[j].y_advance -= d;
|
||||
pos[j].y_offset -= d;
|
||||
break;
|
||||
case HB_DIRECTION_BTT:
|
||||
d = roundf (exit_y) + pos[i].y_offset;
|
||||
pos[i].y_advance -= d;
|
||||
pos[i].y_offset -= d;
|
||||
|
||||
pos[j].y_advance = roundf (entry_y);
|
||||
break;
|
||||
case HB_DIRECTION_INVALID:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Cross-direction adjustment */
|
||||
|
||||
/* We attach child to parent (think graph theory and rooted trees whereas
|
||||
* the root stays on baseline and each node aligns itself against its
|
||||
* parent.
|
||||
*
|
||||
* Optimize things for the case of RightToLeft, as that's most common in
|
||||
* Arabic. */
|
||||
unsigned int child = i;
|
||||
unsigned int parent = j;
|
||||
hb_position_t x_offset = roundf (entry_x - exit_x);
|
||||
hb_position_t y_offset = roundf (entry_y - exit_y);
|
||||
if (!(c->lookup_props & LookupFlag::RightToLeft))
|
||||
{
|
||||
unsigned int k = child;
|
||||
child = parent;
|
||||
parent = k;
|
||||
x_offset = -x_offset;
|
||||
y_offset = -y_offset;
|
||||
}
|
||||
|
||||
/* If child was already connected to someone else, walk through its old
|
||||
* chain and reverse the link direction, such that the whole tree of its
|
||||
* previous connection now attaches to new parent. Watch out for case
|
||||
* where new parent is on the path from old chain...
|
||||
*/
|
||||
reverse_cursive_minor_offset (pos, child, c->direction, parent);
|
||||
|
||||
pos[child].attach_type() = ATTACH_TYPE_CURSIVE;
|
||||
pos[child].attach_chain() = (int) parent - (int) child;
|
||||
buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
|
||||
if (likely (HB_DIRECTION_IS_HORIZONTAL (c->direction)))
|
||||
pos[child].y_offset = y_offset;
|
||||
else
|
||||
pos[child].x_offset = x_offset;
|
||||
|
||||
/* If parent was attached to child, separate them.
|
||||
* https://github.com/harfbuzz/harfbuzz/issues/2469
|
||||
*/
|
||||
if (unlikely (pos[parent].attach_chain() == -pos[child].attach_chain()))
|
||||
{
|
||||
pos[parent].attach_chain() = 0;
|
||||
if (likely (HB_DIRECTION_IS_HORIZONTAL (c->direction)))
|
||||
pos[parent].y_offset = 0;
|
||||
else
|
||||
pos[parent].x_offset = 0;
|
||||
}
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"cursive attached glyph at %u to glyph at %u",
|
||||
i, j);
|
||||
}
|
||||
|
||||
buffer->idx++;
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
template <typename Iterator,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
void serialize (hb_subset_context_t *c,
|
||||
Iterator it,
|
||||
const struct CursivePosFormat1 *src_base)
|
||||
{
|
||||
if (unlikely (!c->serializer->extend_min ((*this)))) return;
|
||||
this->format = 1;
|
||||
this->entryExitRecord.len = it.len ();
|
||||
|
||||
for (const EntryExitRecord& entry_record : + it
|
||||
| hb_map (hb_second))
|
||||
entry_record.subset (c, src_base);
|
||||
|
||||
auto glyphs =
|
||||
+ it
|
||||
| hb_map_retains_sorting (hb_first)
|
||||
;
|
||||
|
||||
coverage.serialize_serialize (c->serializer, glyphs);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
|
||||
auto it =
|
||||
+ hb_zip (this+coverage, entryExitRecord)
|
||||
| hb_filter (glyphset, hb_first)
|
||||
| hb_map_retains_sorting ([&] (hb_pair_t<hb_codepoint_t, const EntryExitRecord&> p) -> hb_pair_t<hb_codepoint_t, const EntryExitRecord&>
|
||||
{ return hb_pair (glyph_map[p.first], p.second);})
|
||||
;
|
||||
|
||||
bool ret = bool (it);
|
||||
out->serialize (c, it, this);
|
||||
return_trace (ret);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_CURSIVEPOSFORMAT1_HH */
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
#ifndef OT_LAYOUT_GPOS_EXTENSIONPOS_HH
|
||||
#define OT_LAYOUT_GPOS_EXTENSIONPOS_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct ExtensionPos : Extension<ExtensionPos>
|
||||
{
|
||||
typedef struct PosLookupSubTable SubTable;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_EXTENSIONPOS_HH */
|
||||
@@ -0,0 +1,171 @@
|
||||
#ifndef OT_LAYOUT_GPOS_GPOS_HH
|
||||
#define OT_LAYOUT_GPOS_GPOS_HH
|
||||
|
||||
#include "../../../hb-ot-layout-common.hh"
|
||||
#include "../../../hb-ot-layout-gsubgpos.hh"
|
||||
#include "Common.hh"
|
||||
#include "PosLookup.hh"
|
||||
|
||||
namespace OT {
|
||||
|
||||
using Layout::GPOS_impl::PosLookup;
|
||||
|
||||
namespace Layout {
|
||||
|
||||
static void
|
||||
propagate_attachment_offsets (hb_glyph_position_t *pos,
|
||||
unsigned int len,
|
||||
unsigned int i,
|
||||
hb_direction_t direction,
|
||||
unsigned nesting_level = HB_MAX_NESTING_LEVEL);
|
||||
|
||||
/*
|
||||
* GPOS -- Glyph Positioning
|
||||
* https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
|
||||
*/
|
||||
|
||||
struct GPOS : GSUBGPOS
|
||||
{
|
||||
static constexpr hb_tag_t tableTag = HB_OT_TAG_GPOS;
|
||||
|
||||
using Lookup = PosLookup;
|
||||
|
||||
const PosLookup& get_lookup (unsigned int i) const
|
||||
{ return static_cast<const PosLookup &> (GSUBGPOS::get_lookup (i)); }
|
||||
|
||||
static inline void position_start (hb_font_t *font, hb_buffer_t *buffer);
|
||||
static inline void position_finish_advances (hb_font_t *font, hb_buffer_t *buffer);
|
||||
static inline void position_finish_offsets (hb_font_t *font, hb_buffer_t *buffer);
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
hb_subset_layout_context_t l (c, tableTag);
|
||||
return GSUBGPOS::subset<PosLookup> (&l);
|
||||
}
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (GSUBGPOS::sanitize<PosLookup> (c));
|
||||
}
|
||||
|
||||
HB_INTERNAL bool is_blocklisted (hb_blob_t *blob,
|
||||
hb_face_t *face) const;
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
|
||||
{
|
||||
for (unsigned i = 0; i < GSUBGPOS::get_lookup_count (); i++)
|
||||
{
|
||||
if (!c->gpos_lookups->has (i)) continue;
|
||||
const PosLookup &l = get_lookup (i);
|
||||
l.dispatch (c);
|
||||
}
|
||||
}
|
||||
|
||||
void closure_lookups (hb_face_t *face,
|
||||
const hb_set_t *glyphs,
|
||||
hb_set_t *lookup_indexes /* IN/OUT */) const
|
||||
{ GSUBGPOS::closure_lookups<PosLookup> (face, glyphs, lookup_indexes); }
|
||||
|
||||
typedef GSUBGPOS::accelerator_t<GPOS> accelerator_t;
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
propagate_attachment_offsets (hb_glyph_position_t *pos,
|
||||
unsigned int len,
|
||||
unsigned int i,
|
||||
hb_direction_t direction,
|
||||
unsigned nesting_level)
|
||||
{
|
||||
/* Adjusts offsets of attached glyphs (both cursive and mark) to accumulate
|
||||
* offset of glyph they are attached to. */
|
||||
int chain = pos[i].attach_chain(), type = pos[i].attach_type();
|
||||
if (likely (!chain))
|
||||
return;
|
||||
|
||||
pos[i].attach_chain() = 0;
|
||||
|
||||
unsigned int j = (int) i + chain;
|
||||
|
||||
if (unlikely (j >= len))
|
||||
return;
|
||||
|
||||
if (unlikely (!nesting_level))
|
||||
return;
|
||||
|
||||
propagate_attachment_offsets (pos, len, j, direction, nesting_level - 1);
|
||||
|
||||
assert (!!(type & GPOS_impl::ATTACH_TYPE_MARK) ^ !!(type & GPOS_impl::ATTACH_TYPE_CURSIVE));
|
||||
|
||||
if (type & GPOS_impl::ATTACH_TYPE_CURSIVE)
|
||||
{
|
||||
if (HB_DIRECTION_IS_HORIZONTAL (direction))
|
||||
pos[i].y_offset += pos[j].y_offset;
|
||||
else
|
||||
pos[i].x_offset += pos[j].x_offset;
|
||||
}
|
||||
else /*if (type & GPOS_impl::ATTACH_TYPE_MARK)*/
|
||||
{
|
||||
pos[i].x_offset += pos[j].x_offset;
|
||||
pos[i].y_offset += pos[j].y_offset;
|
||||
|
||||
assert (j < i);
|
||||
if (HB_DIRECTION_IS_FORWARD (direction))
|
||||
for (unsigned int k = j; k < i; k++) {
|
||||
pos[i].x_offset -= pos[k].x_advance;
|
||||
pos[i].y_offset -= pos[k].y_advance;
|
||||
}
|
||||
else
|
||||
for (unsigned int k = j + 1; k < i + 1; k++) {
|
||||
pos[i].x_offset += pos[k].x_advance;
|
||||
pos[i].y_offset += pos[k].y_advance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GPOS::position_start (hb_font_t *font HB_UNUSED, hb_buffer_t *buffer)
|
||||
{
|
||||
unsigned int count = buffer->len;
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
buffer->pos[i].attach_chain() = buffer->pos[i].attach_type() = 0;
|
||||
}
|
||||
|
||||
void
|
||||
GPOS::position_finish_advances (hb_font_t *font HB_UNUSED, hb_buffer_t *buffer HB_UNUSED)
|
||||
{
|
||||
//_hb_buffer_assert_gsubgpos_vars (buffer);
|
||||
}
|
||||
|
||||
void
|
||||
GPOS::position_finish_offsets (hb_font_t *font, hb_buffer_t *buffer)
|
||||
{
|
||||
_hb_buffer_assert_gsubgpos_vars (buffer);
|
||||
|
||||
unsigned int len;
|
||||
hb_glyph_position_t *pos = hb_buffer_get_glyph_positions (buffer, &len);
|
||||
hb_direction_t direction = buffer->props.direction;
|
||||
|
||||
/* Handle attachments */
|
||||
if (buffer->scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT)
|
||||
for (unsigned i = 0; i < len; i++)
|
||||
propagate_attachment_offsets (pos, len, i, direction);
|
||||
|
||||
if (unlikely (font->slant))
|
||||
{
|
||||
for (unsigned i = 0; i < len; i++)
|
||||
if (unlikely (pos[i].y_offset))
|
||||
pos[i].x_offset += roundf (font->slant_xy * pos[i].y_offset);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct GPOS_accelerator_t : Layout::GPOS::accelerator_t {
|
||||
GPOS_accelerator_t (hb_face_t *face) : Layout::GPOS::accelerator_t (face) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_GPOS_HH */
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
#ifndef OT_LAYOUT_GPOS_LIGATUREARRAY_HH
|
||||
#define OT_LAYOUT_GPOS_LIGATUREARRAY_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
|
||||
typedef AnchorMatrix LigatureAttach; /* component-major--
|
||||
* in order of writing direction--,
|
||||
* mark-minor--
|
||||
* ordered by class--zero-based. */
|
||||
|
||||
/* Array of LigatureAttach tables ordered by LigatureCoverage Index */
|
||||
struct LigatureArray : List16OfOffset16To<LigatureAttach>
|
||||
{
|
||||
template <typename Iterator,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
bool subset (hb_subset_context_t *c,
|
||||
Iterator coverage,
|
||||
unsigned class_count,
|
||||
const hb_map_t *klass_mapping) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
|
||||
auto *out = c->serializer->start_embed (this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
|
||||
bool ret = false;
|
||||
for (const auto _ : + hb_zip (coverage, *this)
|
||||
| hb_filter (glyphset, hb_first))
|
||||
{
|
||||
auto *matrix = out->serialize_append (c->serializer);
|
||||
if (unlikely (!matrix)) return_trace (false);
|
||||
|
||||
const LigatureAttach& src = (this + _.second);
|
||||
auto indexes =
|
||||
+ hb_range (src.rows * class_count)
|
||||
| hb_filter ([=] (unsigned index) { return klass_mapping->has (index % class_count); })
|
||||
;
|
||||
ret |= matrix->serialize_subset (c,
|
||||
_.second,
|
||||
this,
|
||||
src.rows,
|
||||
indexes);
|
||||
}
|
||||
return_trace (ret);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_LIGATUREARRAY_HH */
|
||||
@@ -0,0 +1,128 @@
|
||||
#ifndef OT_LAYOUT_GPOS_MARKARRAY_HH
|
||||
#define OT_LAYOUT_GPOS_MARKARRAY_HH
|
||||
|
||||
#include "AnchorMatrix.hh"
|
||||
#include "MarkRecord.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct MarkArray : Array16Of<MarkRecord> /* Array of MarkRecords--in Coverage order */
|
||||
{
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (Array16Of<MarkRecord>::sanitize (c, this));
|
||||
}
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c,
|
||||
unsigned int mark_index, unsigned int glyph_index,
|
||||
const AnchorMatrix &anchors, unsigned int class_count,
|
||||
unsigned int glyph_pos) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
hb_buffer_t *buffer = c->buffer;
|
||||
const MarkRecord &record = Array16Of<MarkRecord>::operator[](mark_index);
|
||||
unsigned int mark_class = record.klass;
|
||||
|
||||
const Anchor& mark_anchor = this + record.markAnchor;
|
||||
bool found;
|
||||
const Anchor& glyph_anchor = anchors.get_anchor (c, glyph_index, mark_class, class_count, &found);
|
||||
/* If this subtable doesn't have an anchor for this base and this class,
|
||||
* return false such that the subsequent subtables have a chance at it. */
|
||||
if (unlikely (!found)) return_trace (false);
|
||||
|
||||
float mark_x, mark_y, base_x, base_y;
|
||||
|
||||
buffer->unsafe_to_break (glyph_pos, buffer->idx + 1);
|
||||
mark_anchor.get_anchor (c, buffer->cur().codepoint, &mark_x, &mark_y);
|
||||
glyph_anchor.get_anchor (c, buffer->info[glyph_pos].codepoint, &base_x, &base_y);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"attaching mark glyph at %u to glyph at %u",
|
||||
c->buffer->idx, glyph_pos);
|
||||
}
|
||||
|
||||
hb_glyph_position_t &o = buffer->cur_pos();
|
||||
o.x_offset = roundf (base_x - mark_x);
|
||||
o.y_offset = roundf (base_y - mark_y);
|
||||
o.attach_type() = ATTACH_TYPE_MARK;
|
||||
o.attach_chain() = (int) glyph_pos - (int) buffer->idx;
|
||||
buffer->scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT;
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"attached mark glyph at %u to glyph at %u",
|
||||
c->buffer->idx, glyph_pos);
|
||||
}
|
||||
|
||||
buffer->idx++;
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
template <typename Iterator,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
bool subset (hb_subset_context_t *c,
|
||||
Iterator coverage,
|
||||
const hb_map_t *klass_mapping) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
|
||||
auto* out = c->serializer->start_embed (this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
|
||||
auto mark_iter =
|
||||
+ hb_zip (coverage, this->iter ())
|
||||
| hb_filter (glyphset, hb_first)
|
||||
| hb_map (hb_second)
|
||||
;
|
||||
|
||||
bool ret = false;
|
||||
unsigned new_length = 0;
|
||||
for (const auto& mark_record : mark_iter) {
|
||||
ret |= mark_record.subset (c, this, klass_mapping);
|
||||
new_length++;
|
||||
}
|
||||
|
||||
if (unlikely (!c->serializer->check_assign (out->len, new_length,
|
||||
HB_SERIALIZE_ERROR_ARRAY_OVERFLOW)))
|
||||
return_trace (false);
|
||||
|
||||
return_trace (ret);
|
||||
}
|
||||
};
|
||||
|
||||
HB_INTERNAL inline
|
||||
void Markclass_closure_and_remap_indexes (const Coverage &mark_coverage,
|
||||
const MarkArray &mark_array,
|
||||
const hb_set_t &glyphset,
|
||||
hb_map_t* klass_mapping /* INOUT */)
|
||||
{
|
||||
hb_set_t orig_classes;
|
||||
|
||||
+ hb_zip (mark_coverage, mark_array)
|
||||
| hb_filter (glyphset, hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_map (&MarkRecord::get_class)
|
||||
| hb_sink (orig_classes)
|
||||
;
|
||||
|
||||
unsigned idx = 0;
|
||||
for (auto klass : orig_classes.iter ())
|
||||
{
|
||||
if (klass_mapping->has (klass)) continue;
|
||||
klass_mapping->set (klass, idx);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_MARKARRAY_HH */
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef OT_LAYOUT_GPOS_MARKBASEPOS_HH
|
||||
#define OT_LAYOUT_GPOS_MARKBASEPOS_HH
|
||||
|
||||
#include "MarkBasePosFormat1.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct MarkBasePos
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
MarkBasePosFormat1_2<SmallTypes> format1;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
MarkBasePosFormat1_2<MediumTypes> format2;
|
||||
#endif
|
||||
} u;
|
||||
|
||||
public:
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{
|
||||
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
|
||||
TRACE_DISPATCH (this, u.format);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
|
||||
#endif
|
||||
default:return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_MARKBASEPOS_HH */
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
#ifndef OT_LAYOUT_GPOS_MARKBASEPOSFORMAT1_HH
|
||||
#define OT_LAYOUT_GPOS_MARKBASEPOSFORMAT1_HH
|
||||
|
||||
#include "MarkArray.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
typedef AnchorMatrix BaseArray; /* base-major--
|
||||
* in order of BaseCoverage Index--,
|
||||
* mark-minor--
|
||||
* ordered by class--zero-based. */
|
||||
|
||||
template <typename Types>
|
||||
struct MarkBasePosFormat1_2
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
markCoverage; /* Offset to MarkCoverage table--from
|
||||
* beginning of MarkBasePos subtable */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
baseCoverage; /* Offset to BaseCoverage table--from
|
||||
* beginning of MarkBasePos subtable */
|
||||
HBUINT16 classCount; /* Number of classes defined for marks */
|
||||
typename Types::template OffsetTo<MarkArray>
|
||||
markArray; /* Offset to MarkArray table--from
|
||||
* beginning of MarkBasePos subtable */
|
||||
typename Types::template OffsetTo<BaseArray>
|
||||
baseArray; /* Offset to BaseArray table--from
|
||||
* beginning of MarkBasePos subtable */
|
||||
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (4 + 4 * Types::size);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this) &&
|
||||
markCoverage.sanitize (c, this) &&
|
||||
baseCoverage.sanitize (c, this) &&
|
||||
markArray.sanitize (c, this) &&
|
||||
baseArray.sanitize (c, this, (unsigned int) classCount));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
return (this+markCoverage).intersects (glyphs) &&
|
||||
(this+baseCoverage).intersects (glyphs);
|
||||
}
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
|
||||
{
|
||||
+ hb_zip (this+markCoverage, this+markArray)
|
||||
| hb_filter (c->glyph_set, hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_apply ([&] (const MarkRecord& record) { record.collect_variation_indices (c, &(this+markArray)); })
|
||||
;
|
||||
|
||||
hb_map_t klass_mapping;
|
||||
Markclass_closure_and_remap_indexes (this+markCoverage, this+markArray, *c->glyph_set, &klass_mapping);
|
||||
|
||||
unsigned basecount = (this+baseArray).rows;
|
||||
auto base_iter =
|
||||
+ hb_zip (this+baseCoverage, hb_range (basecount))
|
||||
| hb_filter (c->glyph_set, hb_first)
|
||||
| hb_map (hb_second)
|
||||
;
|
||||
|
||||
hb_sorted_vector_t<unsigned> base_indexes;
|
||||
for (const unsigned row : base_iter)
|
||||
{
|
||||
+ hb_range ((unsigned) classCount)
|
||||
| hb_filter (klass_mapping)
|
||||
| hb_map ([&] (const unsigned col) { return row * (unsigned) classCount + col; })
|
||||
| hb_sink (base_indexes)
|
||||
;
|
||||
}
|
||||
(this+baseArray).collect_variation_indices (c, base_indexes.iter ());
|
||||
}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
if (unlikely (!(this+markCoverage).collect_coverage (c->input))) return;
|
||||
if (unlikely (!(this+baseCoverage).collect_coverage (c->input))) return;
|
||||
}
|
||||
|
||||
const Coverage &get_coverage () const { return this+markCoverage; }
|
||||
|
||||
static inline bool accept (hb_buffer_t *buffer, unsigned idx)
|
||||
{
|
||||
/* We only want to attach to the first of a MultipleSubst sequence.
|
||||
* https://github.com/harfbuzz/harfbuzz/issues/740
|
||||
* Reject others...
|
||||
* ...but stop if we find a mark in the MultipleSubst sequence:
|
||||
* https://github.com/harfbuzz/harfbuzz/issues/1020 */
|
||||
return !_hb_glyph_info_multiplied (&buffer->info[idx]) ||
|
||||
0 == _hb_glyph_info_get_lig_comp (&buffer->info[idx]) ||
|
||||
(idx == 0 ||
|
||||
_hb_glyph_info_is_mark (&buffer->info[idx - 1]) ||
|
||||
!_hb_glyph_info_multiplied (&buffer->info[idx - 1]) ||
|
||||
_hb_glyph_info_get_lig_id (&buffer->info[idx]) !=
|
||||
_hb_glyph_info_get_lig_id (&buffer->info[idx - 1]) ||
|
||||
_hb_glyph_info_get_lig_comp (&buffer->info[idx]) !=
|
||||
_hb_glyph_info_get_lig_comp (&buffer->info[idx - 1]) + 1
|
||||
);
|
||||
}
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
hb_buffer_t *buffer = c->buffer;
|
||||
unsigned int mark_index = (this+markCoverage).get_coverage (buffer->cur().codepoint);
|
||||
if (likely (mark_index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
/* Now we search backwards for a non-mark glyph.
|
||||
* We don't use skippy_iter.prev() to avoid O(n^2) behavior. */
|
||||
|
||||
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
|
||||
skippy_iter.set_lookup_props (LookupFlag::IgnoreMarks);
|
||||
|
||||
if (c->last_base_until > buffer->idx)
|
||||
{
|
||||
c->last_base_until = 0;
|
||||
c->last_base = -1;
|
||||
}
|
||||
unsigned j;
|
||||
for (j = buffer->idx; j > c->last_base_until; j--)
|
||||
{
|
||||
auto match = skippy_iter.match (buffer->info[j - 1]);
|
||||
if (match == skippy_iter.MATCH)
|
||||
{
|
||||
// https://github.com/harfbuzz/harfbuzz/issues/4124
|
||||
if (!accept (buffer, j - 1) &&
|
||||
NOT_COVERED == (this+baseCoverage).get_coverage (buffer->info[j - 1].codepoint))
|
||||
match = skippy_iter.SKIP;
|
||||
}
|
||||
if (match == skippy_iter.MATCH)
|
||||
{
|
||||
c->last_base = (signed) j - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
c->last_base_until = buffer->idx;
|
||||
if (c->last_base == -1)
|
||||
{
|
||||
buffer->unsafe_to_concat_from_outbuffer (0, buffer->idx + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
unsigned idx = (unsigned) c->last_base;
|
||||
|
||||
/* Checking that matched glyph is actually a base glyph by GDEF is too strong; disabled */
|
||||
//if (!_hb_glyph_info_is_base_glyph (&buffer->info[idx])) { return_trace (false); }
|
||||
|
||||
unsigned int base_index = (this+baseCoverage).get_coverage (buffer->info[idx].codepoint);
|
||||
if (base_index == NOT_COVERED)
|
||||
{
|
||||
buffer->unsafe_to_concat_from_outbuffer (idx, buffer->idx + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
return_trace ((this+markArray).apply (c, mark_index, base_index, this+baseArray, classCount, idx));
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
out->format = format;
|
||||
|
||||
hb_map_t klass_mapping;
|
||||
Markclass_closure_and_remap_indexes (this+markCoverage, this+markArray, glyphset, &klass_mapping);
|
||||
|
||||
if (!klass_mapping.get_population ()) return_trace (false);
|
||||
out->classCount = klass_mapping.get_population ();
|
||||
|
||||
auto mark_iter =
|
||||
+ hb_zip (this+markCoverage, this+markArray)
|
||||
| hb_filter (glyphset, hb_first)
|
||||
;
|
||||
|
||||
hb_sorted_vector_t<hb_codepoint_t> new_coverage;
|
||||
+ mark_iter
|
||||
| hb_map (hb_first)
|
||||
| hb_map (glyph_map)
|
||||
| hb_sink (new_coverage)
|
||||
;
|
||||
|
||||
if (!out->markCoverage.serialize_serialize (c->serializer, new_coverage.iter ()))
|
||||
return_trace (false);
|
||||
|
||||
if (unlikely (!out->markArray.serialize_subset (c, markArray, this,
|
||||
(this+markCoverage).iter (),
|
||||
&klass_mapping)))
|
||||
return_trace (false);
|
||||
|
||||
unsigned basecount = (this+baseArray).rows;
|
||||
auto base_iter =
|
||||
+ hb_zip (this+baseCoverage, hb_range (basecount))
|
||||
| hb_filter (glyphset, hb_first)
|
||||
;
|
||||
|
||||
new_coverage.reset ();
|
||||
+ base_iter
|
||||
| hb_map (hb_first)
|
||||
| hb_map (glyph_map)
|
||||
| hb_sink (new_coverage)
|
||||
;
|
||||
|
||||
if (!out->baseCoverage.serialize_serialize (c->serializer, new_coverage.iter ()))
|
||||
return_trace (false);
|
||||
|
||||
hb_sorted_vector_t<unsigned> base_indexes;
|
||||
for (const unsigned row : + base_iter
|
||||
| hb_map (hb_second))
|
||||
{
|
||||
+ hb_range ((unsigned) classCount)
|
||||
| hb_filter (klass_mapping)
|
||||
| hb_map ([&] (const unsigned col) { return row * (unsigned) classCount + col; })
|
||||
| hb_sink (base_indexes)
|
||||
;
|
||||
}
|
||||
|
||||
return_trace (out->baseArray.serialize_subset (c, baseArray, this,
|
||||
base_iter.len (),
|
||||
base_indexes.iter ()));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_MARKBASEPOSFORMAT1_HH */
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef OT_LAYOUT_GPOS_MARKLIGPOS_HH
|
||||
#define OT_LAYOUT_GPOS_MARKLIGPOS_HH
|
||||
|
||||
#include "MarkLigPosFormat1.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct MarkLigPos
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
MarkLigPosFormat1_2<SmallTypes> format1;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
MarkLigPosFormat1_2<MediumTypes> format2;
|
||||
#endif
|
||||
} u;
|
||||
|
||||
public:
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{
|
||||
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
|
||||
TRACE_DISPATCH (this, u.format);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
|
||||
#endif
|
||||
default:return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_MARKLIGPOS_HH */
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
#ifndef OT_LAYOUT_GPOS_MARKLIGPOSFORMAT1_HH
|
||||
#define OT_LAYOUT_GPOS_MARKLIGPOSFORMAT1_HH
|
||||
|
||||
#include "LigatureArray.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
|
||||
template <typename Types>
|
||||
struct MarkLigPosFormat1_2
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
markCoverage; /* Offset to Mark Coverage table--from
|
||||
* beginning of MarkLigPos subtable */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
ligatureCoverage; /* Offset to Ligature Coverage
|
||||
* table--from beginning of MarkLigPos
|
||||
* subtable */
|
||||
HBUINT16 classCount; /* Number of defined mark classes */
|
||||
typename Types::template OffsetTo<MarkArray>
|
||||
markArray; /* Offset to MarkArray table--from
|
||||
* beginning of MarkLigPos subtable */
|
||||
typename Types::template OffsetTo<LigatureArray>
|
||||
ligatureArray; /* Offset to LigatureArray table--from
|
||||
* beginning of MarkLigPos subtable */
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (4 + 4 * Types::size);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this) &&
|
||||
markCoverage.sanitize (c, this) &&
|
||||
ligatureCoverage.sanitize (c, this) &&
|
||||
markArray.sanitize (c, this) &&
|
||||
ligatureArray.sanitize (c, this, (unsigned int) classCount));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
return (this+markCoverage).intersects (glyphs) &&
|
||||
(this+ligatureCoverage).intersects (glyphs);
|
||||
}
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
|
||||
{
|
||||
+ hb_zip (this+markCoverage, this+markArray)
|
||||
| hb_filter (c->glyph_set, hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_apply ([&] (const MarkRecord& record) { record.collect_variation_indices (c, &(this+markArray)); })
|
||||
;
|
||||
|
||||
hb_map_t klass_mapping;
|
||||
Markclass_closure_and_remap_indexes (this+markCoverage, this+markArray, *c->glyph_set, &klass_mapping);
|
||||
|
||||
unsigned ligcount = (this+ligatureArray).len;
|
||||
auto lig_iter =
|
||||
+ hb_zip (this+ligatureCoverage, hb_range (ligcount))
|
||||
| hb_filter (c->glyph_set, hb_first)
|
||||
| hb_map (hb_second)
|
||||
;
|
||||
|
||||
const LigatureArray& lig_array = this+ligatureArray;
|
||||
for (const unsigned i : lig_iter)
|
||||
{
|
||||
hb_sorted_vector_t<unsigned> lig_indexes;
|
||||
unsigned row_count = lig_array[i].rows;
|
||||
for (unsigned row : + hb_range (row_count))
|
||||
{
|
||||
+ hb_range ((unsigned) classCount)
|
||||
| hb_filter (klass_mapping)
|
||||
| hb_map ([&] (const unsigned col) { return row * (unsigned) classCount + col; })
|
||||
| hb_sink (lig_indexes)
|
||||
;
|
||||
}
|
||||
|
||||
lig_array[i].collect_variation_indices (c, lig_indexes.iter ());
|
||||
}
|
||||
}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
if (unlikely (!(this+markCoverage).collect_coverage (c->input))) return;
|
||||
if (unlikely (!(this+ligatureCoverage).collect_coverage (c->input))) return;
|
||||
}
|
||||
|
||||
const Coverage &get_coverage () const { return this+markCoverage; }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
hb_buffer_t *buffer = c->buffer;
|
||||
unsigned int mark_index = (this+markCoverage).get_coverage (buffer->cur().codepoint);
|
||||
if (likely (mark_index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
/* Now we search backwards for a non-mark glyph */
|
||||
|
||||
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
|
||||
skippy_iter.set_lookup_props (LookupFlag::IgnoreMarks);
|
||||
|
||||
if (c->last_base_until > buffer->idx)
|
||||
{
|
||||
c->last_base_until = 0;
|
||||
c->last_base = -1;
|
||||
}
|
||||
unsigned j;
|
||||
for (j = buffer->idx; j > c->last_base_until; j--)
|
||||
{
|
||||
auto match = skippy_iter.match (buffer->info[j - 1]);
|
||||
if (match == skippy_iter.MATCH)
|
||||
{
|
||||
c->last_base = (signed) j - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
c->last_base_until = buffer->idx;
|
||||
if (c->last_base == -1)
|
||||
{
|
||||
buffer->unsafe_to_concat_from_outbuffer (0, buffer->idx + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
unsigned idx = (unsigned) c->last_base;
|
||||
|
||||
/* Checking that matched glyph is actually a ligature by GDEF is too strong; disabled */
|
||||
//if (!_hb_glyph_info_is_ligature (&buffer->info[idx])) { return_trace (false); }
|
||||
|
||||
unsigned int lig_index = (this+ligatureCoverage).get_coverage (buffer->info[idx].codepoint);
|
||||
if (lig_index == NOT_COVERED)
|
||||
{
|
||||
buffer->unsafe_to_concat_from_outbuffer (idx, buffer->idx + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
const LigatureArray& lig_array = this+ligatureArray;
|
||||
const LigatureAttach& lig_attach = lig_array[lig_index];
|
||||
|
||||
/* Find component to attach to */
|
||||
unsigned int comp_count = lig_attach.rows;
|
||||
if (unlikely (!comp_count))
|
||||
{
|
||||
buffer->unsafe_to_concat_from_outbuffer (idx, buffer->idx + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
/* We must now check whether the ligature ID of the current mark glyph
|
||||
* is identical to the ligature ID of the found ligature. If yes, we
|
||||
* can directly use the component index. If not, we attach the mark
|
||||
* glyph to the last component of the ligature. */
|
||||
unsigned int comp_index;
|
||||
unsigned int lig_id = _hb_glyph_info_get_lig_id (&buffer->info[idx]);
|
||||
unsigned int mark_id = _hb_glyph_info_get_lig_id (&buffer->cur());
|
||||
unsigned int mark_comp = _hb_glyph_info_get_lig_comp (&buffer->cur());
|
||||
if (lig_id && lig_id == mark_id && mark_comp > 0)
|
||||
comp_index = hb_min (comp_count, _hb_glyph_info_get_lig_comp (&buffer->cur())) - 1;
|
||||
else
|
||||
comp_index = comp_count - 1;
|
||||
|
||||
return_trace ((this+markArray).apply (c, mark_index, comp_index, lig_attach, classCount, idx));
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = c->plan->glyph_map_gsub;
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
out->format = format;
|
||||
|
||||
hb_map_t klass_mapping;
|
||||
Markclass_closure_and_remap_indexes (this+markCoverage, this+markArray, glyphset, &klass_mapping);
|
||||
|
||||
if (!klass_mapping.get_population ()) return_trace (false);
|
||||
out->classCount = klass_mapping.get_population ();
|
||||
|
||||
auto mark_iter =
|
||||
+ hb_zip (this+markCoverage, this+markArray)
|
||||
| hb_filter (glyphset, hb_first)
|
||||
;
|
||||
|
||||
auto new_mark_coverage =
|
||||
+ mark_iter
|
||||
| hb_map_retains_sorting (hb_first)
|
||||
| hb_map_retains_sorting (glyph_map)
|
||||
;
|
||||
|
||||
if (!out->markCoverage.serialize_serialize (c->serializer, new_mark_coverage))
|
||||
return_trace (false);
|
||||
|
||||
if (unlikely (!out->markArray.serialize_subset (c, markArray, this,
|
||||
(this+markCoverage).iter (),
|
||||
&klass_mapping)))
|
||||
return_trace (false);
|
||||
|
||||
auto new_ligature_coverage =
|
||||
+ hb_iter (this + ligatureCoverage)
|
||||
| hb_take ((this + ligatureArray).len)
|
||||
| hb_map_retains_sorting (glyph_map)
|
||||
| hb_filter ([] (hb_codepoint_t glyph) { return glyph != HB_MAP_VALUE_INVALID; })
|
||||
;
|
||||
|
||||
if (!out->ligatureCoverage.serialize_serialize (c->serializer, new_ligature_coverage))
|
||||
return_trace (false);
|
||||
|
||||
return_trace (out->ligatureArray.serialize_subset (c, ligatureArray, this,
|
||||
hb_iter (this+ligatureCoverage),
|
||||
classCount, &klass_mapping));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_MARKLIGPOSFORMAT1_HH */
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef OT_LAYOUT_GPOS_MARKMARKPOS_HH
|
||||
#define OT_LAYOUT_GPOS_MARKMARKPOS_HH
|
||||
|
||||
#include "MarkMarkPosFormat1.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct MarkMarkPos
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
MarkMarkPosFormat1_2<SmallTypes> format1;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
MarkMarkPosFormat1_2<MediumTypes> format2;
|
||||
#endif
|
||||
} u;
|
||||
|
||||
public:
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{
|
||||
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
|
||||
TRACE_DISPATCH (this, u.format);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
|
||||
#endif
|
||||
default:return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_MARKMARKPOS_HH */
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
#ifndef OT_LAYOUT_GPOS_MARKMARKPOSFORMAT1_HH
|
||||
#define OT_LAYOUT_GPOS_MARKMARKPOSFORMAT1_HH
|
||||
|
||||
#include "MarkMarkPosFormat1.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
typedef AnchorMatrix Mark2Array; /* mark2-major--
|
||||
* in order of Mark2Coverage Index--,
|
||||
* mark1-minor--
|
||||
* ordered by class--zero-based. */
|
||||
|
||||
template <typename Types>
|
||||
struct MarkMarkPosFormat1_2
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
mark1Coverage; /* Offset to Combining Mark1 Coverage
|
||||
* table--from beginning of MarkMarkPos
|
||||
* subtable */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
mark2Coverage; /* Offset to Combining Mark2 Coverage
|
||||
* table--from beginning of MarkMarkPos
|
||||
* subtable */
|
||||
HBUINT16 classCount; /* Number of defined mark classes */
|
||||
typename Types::template OffsetTo<MarkArray>
|
||||
mark1Array; /* Offset to Mark1Array table--from
|
||||
* beginning of MarkMarkPos subtable */
|
||||
typename Types::template OffsetTo<Mark2Array>
|
||||
mark2Array; /* Offset to Mark2Array table--from
|
||||
* beginning of MarkMarkPos subtable */
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (4 + 4 * Types::size);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this) &&
|
||||
mark1Coverage.sanitize (c, this) &&
|
||||
mark2Coverage.sanitize (c, this) &&
|
||||
mark1Array.sanitize (c, this) &&
|
||||
hb_barrier () &&
|
||||
mark2Array.sanitize (c, this, (unsigned int) classCount));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
return (this+mark1Coverage).intersects (glyphs) &&
|
||||
(this+mark2Coverage).intersects (glyphs);
|
||||
}
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
|
||||
{
|
||||
+ hb_zip (this+mark1Coverage, this+mark1Array)
|
||||
| hb_filter (c->glyph_set, hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_apply ([&] (const MarkRecord& record) { record.collect_variation_indices (c, &(this+mark1Array)); })
|
||||
;
|
||||
|
||||
hb_map_t klass_mapping;
|
||||
Markclass_closure_and_remap_indexes (this+mark1Coverage, this+mark1Array, *c->glyph_set, &klass_mapping);
|
||||
|
||||
unsigned mark2_count = (this+mark2Array).rows;
|
||||
auto mark2_iter =
|
||||
+ hb_zip (this+mark2Coverage, hb_range (mark2_count))
|
||||
| hb_filter (c->glyph_set, hb_first)
|
||||
| hb_map (hb_second)
|
||||
;
|
||||
|
||||
hb_sorted_vector_t<unsigned> mark2_indexes;
|
||||
for (const unsigned row : mark2_iter)
|
||||
{
|
||||
+ hb_range ((unsigned) classCount)
|
||||
| hb_filter (klass_mapping)
|
||||
| hb_map ([&] (const unsigned col) { return row * (unsigned) classCount + col; })
|
||||
| hb_sink (mark2_indexes)
|
||||
;
|
||||
}
|
||||
(this+mark2Array).collect_variation_indices (c, mark2_indexes.iter ());
|
||||
}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
if (unlikely (!(this+mark1Coverage).collect_coverage (c->input))) return;
|
||||
if (unlikely (!(this+mark2Coverage).collect_coverage (c->input))) return;
|
||||
}
|
||||
|
||||
const Coverage &get_coverage () const { return this+mark1Coverage; }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
hb_buffer_t *buffer = c->buffer;
|
||||
unsigned int mark1_index = (this+mark1Coverage).get_coverage (buffer->cur().codepoint);
|
||||
if (likely (mark1_index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
/* now we search backwards for a suitable mark glyph until a non-mark glyph */
|
||||
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
|
||||
skippy_iter.reset_fast (buffer->idx);
|
||||
skippy_iter.set_lookup_props (c->lookup_props & ~(uint32_t)LookupFlag::IgnoreFlags);
|
||||
unsigned unsafe_from;
|
||||
if (unlikely (!skippy_iter.prev (&unsafe_from)))
|
||||
{
|
||||
buffer->unsafe_to_concat_from_outbuffer (unsafe_from, buffer->idx + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
if (likely (!_hb_glyph_info_is_mark (&buffer->info[skippy_iter.idx])))
|
||||
{
|
||||
buffer->unsafe_to_concat_from_outbuffer (skippy_iter.idx, buffer->idx + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
unsigned int j = skippy_iter.idx;
|
||||
|
||||
unsigned int id1 = _hb_glyph_info_get_lig_id (&buffer->cur());
|
||||
unsigned int id2 = _hb_glyph_info_get_lig_id (&buffer->info[j]);
|
||||
unsigned int comp1 = _hb_glyph_info_get_lig_comp (&buffer->cur());
|
||||
unsigned int comp2 = _hb_glyph_info_get_lig_comp (&buffer->info[j]);
|
||||
|
||||
if (likely (id1 == id2))
|
||||
{
|
||||
if (id1 == 0) /* Marks belonging to the same base. */
|
||||
goto good;
|
||||
else if (comp1 == comp2) /* Marks belonging to the same ligature component. */
|
||||
goto good;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If ligature ids don't match, it may be the case that one of the marks
|
||||
* itself is a ligature. In which case match. */
|
||||
if ((id1 > 0 && !comp1) || (id2 > 0 && !comp2))
|
||||
goto good;
|
||||
}
|
||||
|
||||
/* Didn't match. */
|
||||
buffer->unsafe_to_concat_from_outbuffer (skippy_iter.idx, buffer->idx + 1);
|
||||
return_trace (false);
|
||||
|
||||
good:
|
||||
unsigned int mark2_index = (this+mark2Coverage).get_coverage (buffer->info[j].codepoint);
|
||||
if (mark2_index == NOT_COVERED)
|
||||
{
|
||||
buffer->unsafe_to_concat_from_outbuffer (skippy_iter.idx, buffer->idx + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
return_trace ((this+mark1Array).apply (c, mark1_index, mark2_index, this+mark2Array, classCount, j));
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
out->format = format;
|
||||
|
||||
hb_map_t klass_mapping;
|
||||
Markclass_closure_and_remap_indexes (this+mark1Coverage, this+mark1Array, glyphset, &klass_mapping);
|
||||
|
||||
if (!klass_mapping.get_population ()) return_trace (false);
|
||||
out->classCount = klass_mapping.get_population ();
|
||||
|
||||
auto mark1_iter =
|
||||
+ hb_zip (this+mark1Coverage, this+mark1Array)
|
||||
| hb_filter (glyphset, hb_first)
|
||||
;
|
||||
|
||||
hb_sorted_vector_t<hb_codepoint_t> new_coverage;
|
||||
+ mark1_iter
|
||||
| hb_map (hb_first)
|
||||
| hb_map (glyph_map)
|
||||
| hb_sink (new_coverage)
|
||||
;
|
||||
|
||||
if (!out->mark1Coverage.serialize_serialize (c->serializer, new_coverage.iter ()))
|
||||
return_trace (false);
|
||||
|
||||
if (unlikely (!out->mark1Array.serialize_subset (c, mark1Array, this,
|
||||
(this+mark1Coverage).iter (),
|
||||
&klass_mapping)))
|
||||
return_trace (false);
|
||||
|
||||
unsigned mark2count = (this+mark2Array).rows;
|
||||
auto mark2_iter =
|
||||
+ hb_zip (this+mark2Coverage, hb_range (mark2count))
|
||||
| hb_filter (glyphset, hb_first)
|
||||
;
|
||||
|
||||
new_coverage.reset ();
|
||||
+ mark2_iter
|
||||
| hb_map (hb_first)
|
||||
| hb_map (glyph_map)
|
||||
| hb_sink (new_coverage)
|
||||
;
|
||||
|
||||
if (!out->mark2Coverage.serialize_serialize (c->serializer, new_coverage.iter ()))
|
||||
return_trace (false);
|
||||
|
||||
hb_sorted_vector_t<unsigned> mark2_indexes;
|
||||
for (const unsigned row : + mark2_iter
|
||||
| hb_map (hb_second))
|
||||
{
|
||||
+ hb_range ((unsigned) classCount)
|
||||
| hb_filter (klass_mapping)
|
||||
| hb_map ([&] (const unsigned col) { return row * (unsigned) classCount + col; })
|
||||
| hb_sink (mark2_indexes)
|
||||
;
|
||||
}
|
||||
|
||||
return_trace (out->mark2Array.serialize_subset (c, mark2Array, this,
|
||||
mark2_iter.len (),
|
||||
mark2_indexes.iter ()));
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_MARKMARKPOSFORMAT1_HH */
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef OT_LAYOUT_GPOS_MARKRECORD_HH
|
||||
#define OT_LAYOUT_GPOS_MARKRECORD_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct MarkRecord
|
||||
{
|
||||
friend struct MarkArray;
|
||||
|
||||
public:
|
||||
HBUINT16 klass; /* Class defined for this mark */
|
||||
Offset16To<Anchor>
|
||||
markAnchor; /* Offset to Anchor table--from
|
||||
* beginning of MarkArray table */
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (4);
|
||||
|
||||
unsigned get_class () const { return (unsigned) klass; }
|
||||
bool sanitize (hb_sanitize_context_t *c, const void *base) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this) && markAnchor.sanitize (c, base));
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c,
|
||||
const void *src_base,
|
||||
const hb_map_t *klass_mapping) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
auto *out = c->serializer->embed (this);
|
||||
if (unlikely (!out)) return_trace (false);
|
||||
|
||||
out->klass = klass_mapping->get (klass);
|
||||
return_trace (out->markAnchor.serialize_subset (c, markAnchor, src_base));
|
||||
}
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
|
||||
const void *src_base) const
|
||||
{
|
||||
(src_base+markAnchor).collect_variation_indices (c);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_MARKRECORD_HH */
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef OT_LAYOUT_GPOS_PAIRPOS_HH
|
||||
#define OT_LAYOUT_GPOS_PAIRPOS_HH
|
||||
|
||||
#include "PairPosFormat1.hh"
|
||||
#include "PairPosFormat2.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct PairPos
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
PairPosFormat1_3<SmallTypes> format1;
|
||||
PairPosFormat2_4<SmallTypes> format2;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
PairPosFormat1_3<MediumTypes> format3;
|
||||
PairPosFormat2_4<MediumTypes> format4;
|
||||
#endif
|
||||
} u;
|
||||
|
||||
public:
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{
|
||||
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
|
||||
TRACE_DISPATCH (this, u.format);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
|
||||
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
|
||||
case 4: return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
|
||||
#endif
|
||||
default:return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OT_LAYOUT_GPOS_PAIRPOS_HH
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
#ifndef OT_LAYOUT_GPOS_PAIRPOSFORMAT1_HH
|
||||
#define OT_LAYOUT_GPOS_PAIRPOSFORMAT1_HH
|
||||
|
||||
#include "PairSet.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
|
||||
template <typename Types>
|
||||
struct PairPosFormat1_3
|
||||
{
|
||||
using PairSet = GPOS_impl::PairSet<Types>;
|
||||
using PairValueRecord = GPOS_impl::PairValueRecord<Types>;
|
||||
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
coverage; /* Offset to Coverage table--from
|
||||
* beginning of subtable */
|
||||
ValueFormat valueFormat[2]; /* [0] Defines the types of data in
|
||||
* ValueRecord1--for the first glyph
|
||||
* in the pair--may be zero (0) */
|
||||
/* [1] Defines the types of data in
|
||||
* ValueRecord2--for the second glyph
|
||||
* in the pair--may be zero (0) */
|
||||
Array16Of<typename Types::template OffsetTo<PairSet>>
|
||||
pairSet; /* Array of PairSet tables
|
||||
* ordered by Coverage Index */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (8 + Types::size, pairSet);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
|
||||
if (!c->check_struct (this)) return_trace (false);
|
||||
hb_barrier ();
|
||||
|
||||
unsigned int len1 = valueFormat[0].get_len ();
|
||||
unsigned int len2 = valueFormat[1].get_len ();
|
||||
typename PairSet::sanitize_closure_t closure =
|
||||
{
|
||||
valueFormat,
|
||||
len1,
|
||||
PairSet::get_size (len1, len2)
|
||||
};
|
||||
|
||||
return_trace (coverage.sanitize (c, this) && pairSet.sanitize (c, this, &closure));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
auto &cov = this+coverage;
|
||||
|
||||
if (pairSet.len > glyphs->get_population () * hb_bit_storage ((unsigned) pairSet.len) / 4)
|
||||
{
|
||||
for (hb_codepoint_t g : glyphs->iter())
|
||||
{
|
||||
unsigned i = cov.get_coverage (g);
|
||||
if ((this+pairSet[i]).intersects (glyphs, valueFormat))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return
|
||||
+ hb_zip (cov, pairSet)
|
||||
| hb_filter (*glyphs, hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_map ([glyphs, this] (const typename Types::template OffsetTo<PairSet> &_)
|
||||
{ return (this+_).intersects (glyphs, valueFormat); })
|
||||
| hb_any
|
||||
;
|
||||
}
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
|
||||
{
|
||||
if ((!valueFormat[0].has_device ()) && (!valueFormat[1].has_device ())) return;
|
||||
|
||||
auto it =
|
||||
+ hb_zip (this+coverage, pairSet)
|
||||
| hb_filter (c->glyph_set, hb_first)
|
||||
| hb_map (hb_second)
|
||||
;
|
||||
|
||||
if (!it) return;
|
||||
+ it
|
||||
| hb_map (hb_add (this))
|
||||
| hb_apply ([&] (const PairSet& _) { _.collect_variation_indices (c, valueFormat); })
|
||||
;
|
||||
}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
|
||||
unsigned int count = pairSet.len;
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
(this+pairSet[i]).collect_glyphs (c, valueFormat);
|
||||
}
|
||||
|
||||
const Coverage &get_coverage () const { return this+coverage; }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
hb_buffer_t *buffer = c->buffer;
|
||||
unsigned int index = (this+coverage).get_coverage (buffer->cur().codepoint);
|
||||
if (likely (index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
|
||||
skippy_iter.reset_fast (buffer->idx);
|
||||
unsigned unsafe_to;
|
||||
if (unlikely (!skippy_iter.next (&unsafe_to)))
|
||||
{
|
||||
buffer->unsafe_to_concat (buffer->idx, unsafe_to);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
return_trace ((this+pairSet[index]).apply (c, valueFormat, skippy_iter.idx));
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
out->format = format;
|
||||
|
||||
hb_pair_t<unsigned, unsigned> newFormats = hb_pair (valueFormat[0], valueFormat[1]);
|
||||
|
||||
if (c->plan->normalized_coords)
|
||||
{
|
||||
/* all device flags will be dropped when full instancing, no need to strip
|
||||
* hints, also do not strip emtpy cause we don't compute the new default
|
||||
* value during stripping */
|
||||
newFormats = compute_effective_value_formats (glyphset, false, false, &c->plan->layout_variation_idx_delta_map);
|
||||
}
|
||||
/* do not strip hints for VF */
|
||||
else if (c->plan->flags & HB_SUBSET_FLAGS_NO_HINTING)
|
||||
{
|
||||
hb_blob_t* blob = hb_face_reference_table (c->plan->source, HB_TAG ('f','v','a','r'));
|
||||
bool has_fvar = (blob != hb_blob_get_empty ());
|
||||
hb_blob_destroy (blob);
|
||||
|
||||
bool strip = !has_fvar;
|
||||
/* special case: strip hints when a VF has no GDEF varstore after
|
||||
* subsetting*/
|
||||
if (has_fvar && !c->plan->has_gdef_varstore)
|
||||
strip = true;
|
||||
newFormats = compute_effective_value_formats (glyphset, strip, true);
|
||||
}
|
||||
|
||||
out->valueFormat[0] = newFormats.first;
|
||||
out->valueFormat[1] = newFormats.second;
|
||||
|
||||
hb_sorted_vector_t<hb_codepoint_t> new_coverage;
|
||||
|
||||
+ hb_zip (this+coverage, pairSet)
|
||||
| hb_filter (glyphset, hb_first)
|
||||
| hb_filter ([this, c, out] (const typename Types::template OffsetTo<PairSet>& _)
|
||||
{
|
||||
auto snap = c->serializer->snapshot ();
|
||||
auto *o = out->pairSet.serialize_append (c->serializer);
|
||||
if (unlikely (!o)) return false;
|
||||
bool ret = o->serialize_subset (c, _, this, valueFormat, out->valueFormat);
|
||||
if (!ret)
|
||||
{
|
||||
out->pairSet.pop ();
|
||||
c->serializer->revert (snap);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
hb_second)
|
||||
| hb_map (hb_first)
|
||||
| hb_map (glyph_map)
|
||||
| hb_sink (new_coverage)
|
||||
;
|
||||
|
||||
out->coverage.serialize_serialize (c->serializer, new_coverage.iter ());
|
||||
|
||||
return_trace (bool (new_coverage));
|
||||
}
|
||||
|
||||
|
||||
hb_pair_t<unsigned, unsigned> compute_effective_value_formats (const hb_set_t& glyphset,
|
||||
bool strip_hints, bool strip_empty,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map = nullptr) const
|
||||
{
|
||||
unsigned record_size = PairSet::get_size (valueFormat);
|
||||
|
||||
unsigned format1 = 0;
|
||||
unsigned format2 = 0;
|
||||
for (const auto & _ :
|
||||
+ hb_zip (this+coverage, pairSet)
|
||||
| hb_filter (glyphset, hb_first)
|
||||
| hb_map (hb_second)
|
||||
)
|
||||
{
|
||||
const PairSet& set = (this + _);
|
||||
const PairValueRecord *record = &set.firstPairValueRecord;
|
||||
|
||||
unsigned count = set.len;
|
||||
for (unsigned i = 0; i < count; i++)
|
||||
{
|
||||
if (record->intersects (glyphset))
|
||||
{
|
||||
format1 = format1 | valueFormat[0].get_effective_format (record->get_values_1 (), strip_hints, strip_empty, &set, varidx_delta_map);
|
||||
format2 = format2 | valueFormat[1].get_effective_format (record->get_values_2 (valueFormat[0]), strip_hints, strip_empty, &set, varidx_delta_map);
|
||||
}
|
||||
record = &StructAtOffset<const PairValueRecord> (record, record_size);
|
||||
}
|
||||
|
||||
if (format1 == valueFormat[0] && format2 == valueFormat[1])
|
||||
break;
|
||||
}
|
||||
|
||||
return hb_pair (format1, format2);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OT_LAYOUT_GPOS_PAIRPOSFORMAT1_HH
|
||||
+359
@@ -0,0 +1,359 @@
|
||||
#ifndef OT_LAYOUT_GPOS_PAIRPOSFORMAT2_HH
|
||||
#define OT_LAYOUT_GPOS_PAIRPOSFORMAT2_HH
|
||||
|
||||
#include "ValueFormat.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
template <typename Types>
|
||||
struct PairPosFormat2_4 : ValueBase
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 2 */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
coverage; /* Offset to Coverage table--from
|
||||
* beginning of subtable */
|
||||
ValueFormat valueFormat1; /* ValueRecord definition--for the
|
||||
* first glyph of the pair--may be zero
|
||||
* (0) */
|
||||
ValueFormat valueFormat2; /* ValueRecord definition--for the
|
||||
* second glyph of the pair--may be
|
||||
* zero (0) */
|
||||
typename Types::template OffsetTo<ClassDef>
|
||||
classDef1; /* Offset to ClassDef table--from
|
||||
* beginning of PairPos subtable--for
|
||||
* the first glyph of the pair */
|
||||
typename Types::template OffsetTo<ClassDef>
|
||||
classDef2; /* Offset to ClassDef table--from
|
||||
* beginning of PairPos subtable--for
|
||||
* the second glyph of the pair */
|
||||
HBUINT16 class1Count; /* Number of classes in ClassDef1
|
||||
* table--includes Class0 */
|
||||
HBUINT16 class2Count; /* Number of classes in ClassDef2
|
||||
* table--includes Class0 */
|
||||
ValueRecord values; /* Matrix of value pairs:
|
||||
* class1-major, class2-minor,
|
||||
* Each entry has value1 and value2 */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (10 + 3 * Types::size, values);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
if (!(c->check_struct (this)
|
||||
&& coverage.sanitize (c, this)
|
||||
&& classDef1.sanitize (c, this)
|
||||
&& classDef2.sanitize (c, this))) return_trace (false);
|
||||
|
||||
unsigned int len1 = valueFormat1.get_len ();
|
||||
unsigned int len2 = valueFormat2.get_len ();
|
||||
unsigned int stride = HBUINT16::static_size * (len1 + len2);
|
||||
unsigned int count = (unsigned int) class1Count * (unsigned int) class2Count;
|
||||
return_trace (c->check_range ((const void *) values,
|
||||
count,
|
||||
stride) &&
|
||||
(c->lazy_some_gpos ||
|
||||
(valueFormat1.sanitize_values_stride_unsafe (c, this, &values[0], count, stride) &&
|
||||
valueFormat2.sanitize_values_stride_unsafe (c, this, &values[len1], count, stride))));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
return (this+coverage).intersects (glyphs) &&
|
||||
(this+classDef2).intersects (glyphs);
|
||||
}
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
|
||||
{
|
||||
if (!intersects (c->glyph_set)) return;
|
||||
if ((!valueFormat1.has_device ()) && (!valueFormat2.has_device ())) return;
|
||||
|
||||
hb_set_t klass1_glyphs, klass2_glyphs;
|
||||
if (!(this+classDef1).collect_coverage (&klass1_glyphs)) return;
|
||||
if (!(this+classDef2).collect_coverage (&klass2_glyphs)) return;
|
||||
|
||||
hb_set_t class1_set, class2_set;
|
||||
for (const unsigned cp : + c->glyph_set->iter () | hb_filter (this + coverage))
|
||||
{
|
||||
if (!klass1_glyphs.has (cp)) class1_set.add (0);
|
||||
else
|
||||
{
|
||||
unsigned klass1 = (this+classDef1).get (cp);
|
||||
class1_set.add (klass1);
|
||||
}
|
||||
}
|
||||
|
||||
class2_set.add (0);
|
||||
for (const unsigned cp : + c->glyph_set->iter () | hb_filter (klass2_glyphs))
|
||||
{
|
||||
unsigned klass2 = (this+classDef2).get (cp);
|
||||
class2_set.add (klass2);
|
||||
}
|
||||
|
||||
if (class1_set.is_empty ()
|
||||
|| class2_set.is_empty ()
|
||||
|| (class2_set.get_population() == 1 && class2_set.has(0)))
|
||||
return;
|
||||
|
||||
unsigned len1 = valueFormat1.get_len ();
|
||||
unsigned len2 = valueFormat2.get_len ();
|
||||
const hb_array_t<const Value> values_array = values.as_array ((unsigned)class1Count * (unsigned) class2Count * (len1 + len2));
|
||||
for (const unsigned class1_idx : class1_set.iter ())
|
||||
{
|
||||
for (const unsigned class2_idx : class2_set.iter ())
|
||||
{
|
||||
unsigned start_offset = (class1_idx * (unsigned) class2Count + class2_idx) * (len1 + len2);
|
||||
if (valueFormat1.has_device ())
|
||||
valueFormat1.collect_variation_indices (c, this, values_array.sub_array (start_offset, len1));
|
||||
|
||||
if (valueFormat2.has_device ())
|
||||
valueFormat2.collect_variation_indices (c, this, values_array.sub_array (start_offset+len1, len2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
|
||||
if (unlikely (!(this+classDef2).collect_coverage (c->input))) return;
|
||||
}
|
||||
|
||||
const Coverage &get_coverage () const { return this+coverage; }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
hb_buffer_t *buffer = c->buffer;
|
||||
unsigned int index = (this+coverage).get_coverage (buffer->cur().codepoint);
|
||||
if (likely (index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
|
||||
skippy_iter.reset_fast (buffer->idx);
|
||||
unsigned unsafe_to;
|
||||
if (unlikely (!skippy_iter.next (&unsafe_to)))
|
||||
{
|
||||
buffer->unsafe_to_concat (buffer->idx, unsafe_to);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
unsigned int klass1 = (this+classDef1).get_class (buffer->cur().codepoint);
|
||||
unsigned int klass2 = (this+classDef2).get_class (buffer->info[skippy_iter.idx].codepoint);
|
||||
if (unlikely (klass1 >= class1Count || klass2 >= class2Count))
|
||||
{
|
||||
buffer->unsafe_to_concat (buffer->idx, skippy_iter.idx + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
unsigned int len1 = valueFormat1.get_len ();
|
||||
unsigned int len2 = valueFormat2.get_len ();
|
||||
unsigned int record_len = len1 + len2;
|
||||
|
||||
const Value *v = &values[record_len * (klass1 * class2Count + klass2)];
|
||||
|
||||
bool applied_first = false, applied_second = false;
|
||||
|
||||
|
||||
/* Isolate simple kerning and apply it half to each side.
|
||||
* Results in better cursor positioning / underline drawing.
|
||||
*
|
||||
* Disabled, because causes issues... :-(
|
||||
* https://github.com/harfbuzz/harfbuzz/issues/3408
|
||||
* https://github.com/harfbuzz/harfbuzz/pull/3235#issuecomment-1029814978
|
||||
*/
|
||||
#ifndef HB_SPLIT_KERN
|
||||
if (false)
|
||||
#endif
|
||||
{
|
||||
if (!len2)
|
||||
{
|
||||
const hb_direction_t dir = buffer->props.direction;
|
||||
const bool horizontal = HB_DIRECTION_IS_HORIZONTAL (dir);
|
||||
const bool backward = HB_DIRECTION_IS_BACKWARD (dir);
|
||||
unsigned mask = horizontal ? ValueFormat::xAdvance : ValueFormat::yAdvance;
|
||||
if (backward)
|
||||
mask |= mask >> 2; /* Add eg. xPlacement in RTL. */
|
||||
/* Add Devices. */
|
||||
mask |= mask << 4;
|
||||
|
||||
if (valueFormat1 & ~mask)
|
||||
goto bail;
|
||||
|
||||
/* Is simple kern. Apply value on an empty position slot,
|
||||
* then split it between sides. */
|
||||
|
||||
hb_glyph_position_t pos{};
|
||||
if (valueFormat1.apply_value (c, this, v, pos))
|
||||
{
|
||||
hb_position_t *src = &pos.x_advance;
|
||||
hb_position_t *dst1 = &buffer->cur_pos().x_advance;
|
||||
hb_position_t *dst2 = &buffer->pos[skippy_iter.idx].x_advance;
|
||||
unsigned i = horizontal ? 0 : 1;
|
||||
|
||||
hb_position_t kern = src[i];
|
||||
hb_position_t kern1 = kern >> 1;
|
||||
hb_position_t kern2 = kern - kern1;
|
||||
|
||||
if (!backward)
|
||||
{
|
||||
dst1[i] += kern1;
|
||||
dst2[i] += kern2;
|
||||
dst2[i + 2] += kern2;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst1[i] += kern1;
|
||||
dst1[i + 2] += src[i + 2] - kern2;
|
||||
dst2[i] += kern2;
|
||||
}
|
||||
|
||||
applied_first = applied_second = kern != 0;
|
||||
goto success;
|
||||
}
|
||||
goto boring;
|
||||
}
|
||||
}
|
||||
bail:
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"try kerning glyphs at %u,%u",
|
||||
c->buffer->idx, skippy_iter.idx);
|
||||
}
|
||||
|
||||
applied_first = len1 && valueFormat1.apply_value (c, this, v, buffer->cur_pos());
|
||||
applied_second = len2 && valueFormat2.apply_value (c, this, v + len1, buffer->pos[skippy_iter.idx]);
|
||||
|
||||
if (applied_first || applied_second)
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"kerned glyphs at %u,%u",
|
||||
c->buffer->idx, skippy_iter.idx);
|
||||
}
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"tried kerning glyphs at %u,%u",
|
||||
c->buffer->idx, skippy_iter.idx);
|
||||
}
|
||||
|
||||
success:
|
||||
if (applied_first || applied_second)
|
||||
buffer->unsafe_to_break (buffer->idx, skippy_iter.idx + 1);
|
||||
else
|
||||
boring:
|
||||
buffer->unsafe_to_concat (buffer->idx, skippy_iter.idx + 1);
|
||||
|
||||
if (len2)
|
||||
{
|
||||
skippy_iter.idx++;
|
||||
// https://github.com/harfbuzz/harfbuzz/issues/3824
|
||||
// https://github.com/harfbuzz/harfbuzz/issues/3888#issuecomment-1326781116
|
||||
buffer->unsafe_to_break (buffer->idx, skippy_iter.idx + 1);
|
||||
}
|
||||
|
||||
buffer->idx = skippy_iter.idx;
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
out->format = format;
|
||||
|
||||
hb_map_t klass1_map;
|
||||
out->classDef1.serialize_subset (c, classDef1, this, &klass1_map, true, true, &(this + coverage));
|
||||
out->class1Count = klass1_map.get_population ();
|
||||
|
||||
hb_map_t klass2_map;
|
||||
out->classDef2.serialize_subset (c, classDef2, this, &klass2_map, true, false);
|
||||
out->class2Count = klass2_map.get_population ();
|
||||
|
||||
unsigned len1 = valueFormat1.get_len ();
|
||||
unsigned len2 = valueFormat2.get_len ();
|
||||
|
||||
hb_pair_t<unsigned, unsigned> newFormats = hb_pair (valueFormat1, valueFormat2);
|
||||
|
||||
if (c->plan->normalized_coords)
|
||||
{
|
||||
/* in case of full instancing, all var device flags will be dropped so no
|
||||
* need to strip hints here */
|
||||
newFormats = compute_effective_value_formats (klass1_map, klass2_map, false, false, &c->plan->layout_variation_idx_delta_map);
|
||||
}
|
||||
/* do not strip hints for VF */
|
||||
else if (c->plan->flags & HB_SUBSET_FLAGS_NO_HINTING)
|
||||
{
|
||||
hb_blob_t* blob = hb_face_reference_table (c->plan->source, HB_TAG ('f','v','a','r'));
|
||||
bool has_fvar = (blob != hb_blob_get_empty ());
|
||||
hb_blob_destroy (blob);
|
||||
|
||||
bool strip = !has_fvar;
|
||||
/* special case: strip hints when a VF has no GDEF varstore after
|
||||
* subsetting*/
|
||||
if (has_fvar && !c->plan->has_gdef_varstore)
|
||||
strip = true;
|
||||
newFormats = compute_effective_value_formats (klass1_map, klass2_map, strip, true);
|
||||
}
|
||||
|
||||
out->valueFormat1 = newFormats.first;
|
||||
out->valueFormat2 = newFormats.second;
|
||||
|
||||
unsigned total_len = len1 + len2;
|
||||
hb_vector_t<unsigned> class2_idxs (+ hb_range ((unsigned) class2Count) | hb_filter (klass2_map));
|
||||
for (unsigned class1_idx : + hb_range ((unsigned) class1Count) | hb_filter (klass1_map))
|
||||
{
|
||||
for (unsigned class2_idx : class2_idxs)
|
||||
{
|
||||
unsigned idx = (class1_idx * (unsigned) class2Count + class2_idx) * total_len;
|
||||
valueFormat1.copy_values (c->serializer, out->valueFormat1, this, &values[idx], &c->plan->layout_variation_idx_delta_map);
|
||||
valueFormat2.copy_values (c->serializer, out->valueFormat2, this, &values[idx + len1], &c->plan->layout_variation_idx_delta_map);
|
||||
}
|
||||
}
|
||||
|
||||
bool ret = out->coverage.serialize_subset(c, coverage, this);
|
||||
return_trace (out->class1Count && out->class2Count && ret);
|
||||
}
|
||||
|
||||
|
||||
hb_pair_t<unsigned, unsigned> compute_effective_value_formats (const hb_map_t& klass1_map,
|
||||
const hb_map_t& klass2_map,
|
||||
bool strip_hints, bool strip_empty,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map = nullptr) const
|
||||
{
|
||||
unsigned len1 = valueFormat1.get_len ();
|
||||
unsigned len2 = valueFormat2.get_len ();
|
||||
unsigned record_size = len1 + len2;
|
||||
|
||||
unsigned format1 = 0;
|
||||
unsigned format2 = 0;
|
||||
|
||||
for (unsigned class1_idx : + hb_range ((unsigned) class1Count) | hb_filter (klass1_map))
|
||||
{
|
||||
for (unsigned class2_idx : + hb_range ((unsigned) class2Count) | hb_filter (klass2_map))
|
||||
{
|
||||
unsigned idx = (class1_idx * (unsigned) class2Count + class2_idx) * record_size;
|
||||
format1 = format1 | valueFormat1.get_effective_format (&values[idx], strip_hints, strip_empty, this, varidx_delta_map);
|
||||
format2 = format2 | valueFormat2.get_effective_format (&values[idx + len1], strip_hints, strip_empty, this, varidx_delta_map);
|
||||
}
|
||||
|
||||
if (format1 == valueFormat1 && format2 == valueFormat2)
|
||||
break;
|
||||
}
|
||||
|
||||
return hb_pair (format1, format2);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OT_LAYOUT_GPOS_PAIRPOSFORMAT2_HH
|
||||
@@ -0,0 +1,210 @@
|
||||
#ifndef OT_LAYOUT_GPOS_PAIRSET_HH
|
||||
#define OT_LAYOUT_GPOS_PAIRSET_HH
|
||||
|
||||
#include "PairValueRecord.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
|
||||
template <typename Types>
|
||||
struct PairSet : ValueBase
|
||||
{
|
||||
template <typename Types2>
|
||||
friend struct PairPosFormat1_3;
|
||||
|
||||
using PairValueRecord = GPOS_impl::PairValueRecord<Types>;
|
||||
|
||||
protected:
|
||||
HBUINT16 len; /* Number of PairValueRecords */
|
||||
PairValueRecord firstPairValueRecord;
|
||||
/* Array of PairValueRecords--ordered
|
||||
* by GlyphID of the second glyph */
|
||||
public:
|
||||
DEFINE_SIZE_MIN (2);
|
||||
|
||||
static unsigned get_size (unsigned len1, unsigned len2)
|
||||
{
|
||||
return Types::HBGlyphID::static_size + Value::static_size * (len1 + len2);
|
||||
}
|
||||
static unsigned get_size (const ValueFormat valueFormats[2])
|
||||
{
|
||||
unsigned len1 = valueFormats[0].get_len ();
|
||||
unsigned len2 = valueFormats[1].get_len ();
|
||||
return get_size (len1, len2);
|
||||
}
|
||||
|
||||
struct sanitize_closure_t
|
||||
{
|
||||
const ValueFormat *valueFormats;
|
||||
unsigned int len1; /* valueFormats[0].get_len() */
|
||||
unsigned int stride; /* bytes */
|
||||
};
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c, const sanitize_closure_t *closure) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
if (!(c->check_struct (this) &&
|
||||
hb_barrier () &&
|
||||
c->check_range (&firstPairValueRecord,
|
||||
len,
|
||||
closure->stride))) return_trace (false);
|
||||
hb_barrier ();
|
||||
|
||||
unsigned int count = len;
|
||||
const PairValueRecord *record = &firstPairValueRecord;
|
||||
return_trace (c->lazy_some_gpos ||
|
||||
(closure->valueFormats[0].sanitize_values_stride_unsafe (c, this, &record->values[0], count, closure->stride) &&
|
||||
closure->valueFormats[1].sanitize_values_stride_unsafe (c, this, &record->values[closure->len1], count, closure->stride)));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs,
|
||||
const ValueFormat *valueFormats) const
|
||||
{
|
||||
unsigned record_size = get_size (valueFormats);
|
||||
|
||||
const PairValueRecord *record = &firstPairValueRecord;
|
||||
unsigned int count = len;
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
{
|
||||
if (glyphs->has (record->secondGlyph))
|
||||
return true;
|
||||
record = &StructAtOffset<const PairValueRecord> (record, record_size);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c,
|
||||
const ValueFormat *valueFormats) const
|
||||
{
|
||||
unsigned record_size = get_size (valueFormats);
|
||||
|
||||
const PairValueRecord *record = &firstPairValueRecord;
|
||||
c->input->add_array (&record->secondGlyph, len, record_size);
|
||||
}
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
|
||||
const ValueFormat *valueFormats) const
|
||||
{
|
||||
unsigned record_size = get_size (valueFormats);
|
||||
|
||||
const PairValueRecord *record = &firstPairValueRecord;
|
||||
unsigned count = len;
|
||||
for (unsigned i = 0; i < count; i++)
|
||||
{
|
||||
if (c->glyph_set->has (record->secondGlyph))
|
||||
{ record->collect_variation_indices (c, valueFormats, this); }
|
||||
|
||||
record = &StructAtOffset<const PairValueRecord> (record, record_size);
|
||||
}
|
||||
}
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c,
|
||||
const ValueFormat *valueFormats,
|
||||
unsigned int pos) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
hb_buffer_t *buffer = c->buffer;
|
||||
unsigned int len1 = valueFormats[0].get_len ();
|
||||
unsigned int len2 = valueFormats[1].get_len ();
|
||||
unsigned record_size = get_size (len1, len2);
|
||||
|
||||
const PairValueRecord *record = hb_bsearch (buffer->info[pos].codepoint,
|
||||
&firstPairValueRecord,
|
||||
len,
|
||||
record_size);
|
||||
if (record)
|
||||
{
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"try kerning glyphs at %u,%u",
|
||||
c->buffer->idx, pos);
|
||||
}
|
||||
|
||||
bool applied_first = len1 && valueFormats[0].apply_value (c, this, &record->values[0], buffer->cur_pos());
|
||||
bool applied_second = len2 && valueFormats[1].apply_value (c, this, &record->values[len1], buffer->pos[pos]);
|
||||
|
||||
if (applied_first || applied_second)
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"kerned glyphs at %u,%u",
|
||||
c->buffer->idx, pos);
|
||||
}
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"tried kerning glyphs at %u,%u",
|
||||
c->buffer->idx, pos);
|
||||
}
|
||||
|
||||
if (applied_first || applied_second)
|
||||
buffer->unsafe_to_break (buffer->idx, pos + 1);
|
||||
|
||||
if (len2)
|
||||
{
|
||||
pos++;
|
||||
// https://github.com/harfbuzz/harfbuzz/issues/3824
|
||||
// https://github.com/harfbuzz/harfbuzz/issues/3888#issuecomment-1326781116
|
||||
buffer->unsafe_to_break (buffer->idx, pos + 1);
|
||||
}
|
||||
|
||||
buffer->idx = pos;
|
||||
return_trace (true);
|
||||
}
|
||||
buffer->unsafe_to_concat (buffer->idx, pos + 1);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c,
|
||||
const ValueFormat valueFormats[2],
|
||||
const ValueFormat newFormats[2]) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
auto snap = c->serializer->snapshot ();
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
out->len = 0;
|
||||
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
unsigned len1 = valueFormats[0].get_len ();
|
||||
unsigned len2 = valueFormats[1].get_len ();
|
||||
unsigned record_size = get_size (len1, len2);
|
||||
|
||||
typename PairValueRecord::context_t context =
|
||||
{
|
||||
this,
|
||||
valueFormats,
|
||||
newFormats,
|
||||
len1,
|
||||
&glyph_map,
|
||||
&c->plan->layout_variation_idx_delta_map
|
||||
};
|
||||
|
||||
const PairValueRecord *record = &firstPairValueRecord;
|
||||
unsigned count = len, num = 0;
|
||||
for (unsigned i = 0; i < count; i++)
|
||||
{
|
||||
if (glyphset.has (record->secondGlyph)
|
||||
&& record->subset (c, &context)) num++;
|
||||
record = &StructAtOffset<const PairValueRecord> (record, record_size);
|
||||
}
|
||||
|
||||
out->len = num;
|
||||
if (!num) c->serializer->revert (snap);
|
||||
return_trace (num);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OT_LAYOUT_GPOS_PAIRSET_HH
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
#ifndef OT_LAYOUT_GPOS_PAIRVALUERECORD_HH
|
||||
#define OT_LAYOUT_GPOS_PAIRVALUERECORD_HH
|
||||
|
||||
#include "ValueFormat.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
|
||||
template <typename Types>
|
||||
struct PairValueRecord
|
||||
{
|
||||
template <typename Types2>
|
||||
friend struct PairSet;
|
||||
|
||||
protected:
|
||||
typename Types::HBGlyphID
|
||||
secondGlyph; /* GlyphID of second glyph in the
|
||||
* pair--first glyph is listed in the
|
||||
* Coverage table */
|
||||
ValueRecord values; /* Positioning data for the first glyph
|
||||
* followed by for second glyph */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (Types::HBGlyphID::static_size, values);
|
||||
|
||||
int cmp (hb_codepoint_t k) const
|
||||
{ return secondGlyph.cmp (k); }
|
||||
|
||||
struct context_t
|
||||
{
|
||||
const ValueBase *base;
|
||||
const ValueFormat *valueFormats;
|
||||
const ValueFormat *newFormats;
|
||||
unsigned len1; /* valueFormats[0].get_len() */
|
||||
const hb_map_t *glyph_map;
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map;
|
||||
};
|
||||
|
||||
bool subset (hb_subset_context_t *c,
|
||||
context_t *closure) const
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
auto *s = c->serializer;
|
||||
auto *out = s->start_embed (*this);
|
||||
if (unlikely (!s->extend_min (out))) return_trace (false);
|
||||
|
||||
out->secondGlyph = (*closure->glyph_map)[secondGlyph];
|
||||
|
||||
closure->valueFormats[0].copy_values (s,
|
||||
closure->newFormats[0],
|
||||
closure->base, &values[0],
|
||||
closure->layout_variation_idx_delta_map);
|
||||
closure->valueFormats[1].copy_values (s,
|
||||
closure->newFormats[1],
|
||||
closure->base,
|
||||
&values[closure->len1],
|
||||
closure->layout_variation_idx_delta_map);
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
|
||||
const ValueFormat *valueFormats,
|
||||
const ValueBase *base) const
|
||||
{
|
||||
unsigned record1_len = valueFormats[0].get_len ();
|
||||
unsigned record2_len = valueFormats[1].get_len ();
|
||||
const hb_array_t<const Value> values_array = values.as_array (record1_len + record2_len);
|
||||
|
||||
if (valueFormats[0].has_device ())
|
||||
valueFormats[0].collect_variation_indices (c, base, values_array.sub_array (0, record1_len));
|
||||
|
||||
if (valueFormats[1].has_device ())
|
||||
valueFormats[1].collect_variation_indices (c, base, values_array.sub_array (record1_len, record2_len));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t& glyphset) const
|
||||
{
|
||||
return glyphset.has(secondGlyph);
|
||||
}
|
||||
|
||||
const Value* get_values_1 () const
|
||||
{
|
||||
return &values[0];
|
||||
}
|
||||
|
||||
const Value* get_values_2 (ValueFormat format1) const
|
||||
{
|
||||
return &values[format1.get_len ()];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // OT_LAYOUT_GPOS_PAIRVALUERECORD_HH
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef OT_LAYOUT_GPOS_POSLOOKUP_HH
|
||||
#define OT_LAYOUT_GPOS_POSLOOKUP_HH
|
||||
|
||||
#include "PosLookupSubTable.hh"
|
||||
#include "../../../hb-ot-layout-common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct PosLookup : Lookup
|
||||
{
|
||||
using SubTable = PosLookupSubTable;
|
||||
|
||||
const SubTable& get_subtable (unsigned int i) const
|
||||
{ return Lookup::get_subtable<SubTable> (i); }
|
||||
|
||||
bool is_reverse () const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
return_trace (dispatch (c));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
hb_intersects_context_t c (glyphs);
|
||||
return dispatch (&c);
|
||||
}
|
||||
|
||||
hb_collect_glyphs_context_t::return_t collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{ return dispatch (c); }
|
||||
|
||||
hb_closure_lookups_context_t::return_t closure_lookups (hb_closure_lookups_context_t *c, unsigned this_index) const
|
||||
{
|
||||
if (c->is_lookup_visited (this_index))
|
||||
return hb_closure_lookups_context_t::default_return_value ();
|
||||
|
||||
c->set_lookup_visited (this_index);
|
||||
if (!intersects (c->glyphs))
|
||||
{
|
||||
c->set_lookup_inactive (this_index);
|
||||
return hb_closure_lookups_context_t::default_return_value ();
|
||||
}
|
||||
|
||||
hb_closure_lookups_context_t::return_t ret = dispatch (c);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename set_t>
|
||||
void collect_coverage (set_t *glyphs) const
|
||||
{
|
||||
hb_collect_coverage_context_t<set_t> c (glyphs);
|
||||
dispatch (&c);
|
||||
}
|
||||
|
||||
template <typename context_t>
|
||||
static typename context_t::return_t dispatch_recurse_func (context_t *c, unsigned int lookup_index);
|
||||
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{ return Lookup::dispatch<SubTable> (c, std::forward<Ts> (ds)...); }
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{ return Lookup::subset<SubTable> (c); }
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{ return Lookup::sanitize<SubTable> (c); }
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_POSLOOKUP_HH */
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
#ifndef OT_LAYOUT_GPOS_POSLOOKUPSUBTABLE_HH
|
||||
#define OT_LAYOUT_GPOS_POSLOOKUPSUBTABLE_HH
|
||||
|
||||
#include "SinglePos.hh"
|
||||
#include "PairPos.hh"
|
||||
#include "CursivePos.hh"
|
||||
#include "MarkBasePos.hh"
|
||||
#include "MarkLigPos.hh"
|
||||
#include "MarkMarkPos.hh"
|
||||
#include "ContextPos.hh"
|
||||
#include "ChainContextPos.hh"
|
||||
#include "ExtensionPos.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct PosLookupSubTable
|
||||
{
|
||||
friend struct ::OT::Lookup;
|
||||
friend struct PosLookup;
|
||||
|
||||
enum Type {
|
||||
Single = 1,
|
||||
Pair = 2,
|
||||
Cursive = 3,
|
||||
MarkBase = 4,
|
||||
MarkLig = 5,
|
||||
MarkMark = 6,
|
||||
Context = 7,
|
||||
ChainContext = 8,
|
||||
Extension = 9
|
||||
};
|
||||
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, unsigned int lookup_type, Ts&&... ds) const
|
||||
{
|
||||
TRACE_DISPATCH (this, lookup_type);
|
||||
switch (lookup_type) {
|
||||
case Single: return_trace (u.single.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case Pair: return_trace (u.pair.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case Cursive: return_trace (u.cursive.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case MarkBase: return_trace (u.markBase.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case MarkLig: return_trace (u.markLig.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case MarkMark: return_trace (u.markMark.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case Context: return_trace (u.context.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case ChainContext: return_trace (u.chainContext.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case Extension: return_trace (u.extension.dispatch (c, std::forward<Ts> (ds)...));
|
||||
default: return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs, unsigned int lookup_type) const
|
||||
{
|
||||
hb_intersects_context_t c (glyphs);
|
||||
return dispatch (&c, lookup_type);
|
||||
}
|
||||
|
||||
protected:
|
||||
union {
|
||||
SinglePos single;
|
||||
PairPos pair;
|
||||
CursivePos cursive;
|
||||
MarkBasePos markBase;
|
||||
MarkLigPos markLig;
|
||||
MarkMarkPos markMark;
|
||||
ContextPos context;
|
||||
ChainContextPos chainContext;
|
||||
ExtensionPos extension;
|
||||
} u;
|
||||
public:
|
||||
DEFINE_SIZE_MIN (0);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* HB_OT_LAYOUT_GPOS_POSLOOKUPSUBTABLE_HH */
|
||||
@@ -0,0 +1,98 @@
|
||||
#ifndef OT_LAYOUT_GPOS_SINGLEPOS_HH
|
||||
#define OT_LAYOUT_GPOS_SINGLEPOS_HH
|
||||
|
||||
#include "SinglePosFormat1.hh"
|
||||
#include "SinglePosFormat2.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct SinglePos
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
SinglePosFormat1 format1;
|
||||
SinglePosFormat2 format2;
|
||||
} u;
|
||||
|
||||
public:
|
||||
template<typename Iterator,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
unsigned get_format (Iterator glyph_val_iter_pairs)
|
||||
{
|
||||
hb_array_t<const Value> first_val_iter = hb_second (*glyph_val_iter_pairs);
|
||||
|
||||
for (const auto iter : glyph_val_iter_pairs)
|
||||
for (const auto _ : hb_zip (iter.second, first_val_iter))
|
||||
if (_.first != _.second)
|
||||
return 2;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<typename Iterator,
|
||||
typename SrcLookup,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
void serialize (hb_serialize_context_t *c,
|
||||
const SrcLookup* src,
|
||||
Iterator glyph_val_iter_pairs,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map,
|
||||
unsigned newFormat)
|
||||
{
|
||||
if (unlikely (!c->extend_min (u.format))) return;
|
||||
unsigned format = 2;
|
||||
ValueFormat new_format;
|
||||
new_format = newFormat;
|
||||
|
||||
if (glyph_val_iter_pairs)
|
||||
format = get_format (glyph_val_iter_pairs);
|
||||
|
||||
u.format = format;
|
||||
switch (u.format) {
|
||||
case 1: u.format1.serialize (c,
|
||||
src,
|
||||
glyph_val_iter_pairs,
|
||||
new_format,
|
||||
layout_variation_idx_delta_map);
|
||||
return;
|
||||
case 2: u.format2.serialize (c,
|
||||
src,
|
||||
glyph_val_iter_pairs,
|
||||
new_format,
|
||||
layout_variation_idx_delta_map);
|
||||
return;
|
||||
default:return;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{
|
||||
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
|
||||
TRACE_DISPATCH (this, u.format);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
|
||||
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
|
||||
default:return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Iterator, typename SrcLookup>
|
||||
static void
|
||||
SinglePos_serialize (hb_serialize_context_t *c,
|
||||
const SrcLookup *src,
|
||||
Iterator it,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map,
|
||||
unsigned new_format)
|
||||
{ c->start_embed<SinglePos> ()->serialize (c, src, it, layout_variation_idx_delta_map, new_format); }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_SINGLEPOS_HH */
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
#ifndef OT_LAYOUT_GPOS_SINGLEPOSFORMAT1_HH
|
||||
#define OT_LAYOUT_GPOS_SINGLEPOSFORMAT1_HH
|
||||
|
||||
#include "Common.hh"
|
||||
#include "ValueFormat.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct SinglePosFormat1 : ValueBase
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
Offset16To<Coverage>
|
||||
coverage; /* Offset to Coverage table--from
|
||||
* beginning of subtable */
|
||||
ValueFormat valueFormat; /* Defines the types of data in the
|
||||
* ValueRecord */
|
||||
ValueRecord values; /* Defines positioning
|
||||
* value(s)--applied to all glyphs in
|
||||
* the Coverage table */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (6, values);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this) &&
|
||||
coverage.sanitize (c, this) &&
|
||||
hb_barrier () &&
|
||||
/* The coverage table may use a range to represent a set
|
||||
* of glyphs, which means a small number of bytes can
|
||||
* generate a large glyph set. Manually modify the
|
||||
* sanitizer max ops to take this into account.
|
||||
*
|
||||
* Note: This check *must* be right after coverage sanitize. */
|
||||
c->check_ops ((this + coverage).get_population () >> 1) &&
|
||||
valueFormat.sanitize_value (c, this, values));
|
||||
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{ return (this+coverage).intersects (glyphs); }
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
|
||||
{
|
||||
if (!valueFormat.has_device ()) return;
|
||||
|
||||
hb_set_t intersection;
|
||||
(this+coverage).intersect_set (*c->glyph_set, intersection);
|
||||
if (!intersection) return;
|
||||
|
||||
valueFormat.collect_variation_indices (c, this, values.as_array (valueFormat.get_len ()));
|
||||
}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{ if (unlikely (!(this+coverage).collect_coverage (c->input))) return; }
|
||||
|
||||
const Coverage &get_coverage () const { return this+coverage; }
|
||||
|
||||
ValueFormat get_value_format () const { return valueFormat; }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
hb_buffer_t *buffer = c->buffer;
|
||||
unsigned int index = (this+coverage).get_coverage (buffer->cur().codepoint);
|
||||
if (likely (index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"positioning glyph at %u",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
valueFormat.apply_value (c, this, values, buffer->cur_pos());
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"positioned glyph at %u",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
buffer->idx++;
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool
|
||||
position_single (hb_font_t *font,
|
||||
hb_blob_t *table_blob,
|
||||
hb_direction_t direction,
|
||||
hb_codepoint_t gid,
|
||||
hb_glyph_position_t &pos) const
|
||||
{
|
||||
unsigned int index = (this+coverage).get_coverage (gid);
|
||||
if (likely (index == NOT_COVERED)) return false;
|
||||
|
||||
/* This is ugly... */
|
||||
hb_buffer_t buffer;
|
||||
buffer.props.direction = direction;
|
||||
OT::hb_ot_apply_context_t c (1, font, &buffer, table_blob);
|
||||
|
||||
valueFormat.apply_value (&c, this, values, pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Iterator,
|
||||
typename SrcLookup,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
void serialize (hb_serialize_context_t *c,
|
||||
const SrcLookup *src,
|
||||
Iterator it,
|
||||
ValueFormat newFormat,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map)
|
||||
{
|
||||
if (unlikely (!c->extend_min (this))) return;
|
||||
if (unlikely (!c->check_assign (valueFormat,
|
||||
newFormat,
|
||||
HB_SERIALIZE_ERROR_INT_OVERFLOW))) return;
|
||||
|
||||
for (const hb_array_t<const Value>& _ : + it | hb_map (hb_second))
|
||||
{
|
||||
src->get_value_format ().copy_values (c, newFormat, src, &_, layout_variation_idx_delta_map);
|
||||
// Only serialize the first entry in the iterator, the rest are assumed to
|
||||
// be the same.
|
||||
break;
|
||||
}
|
||||
|
||||
auto glyphs =
|
||||
+ it
|
||||
| hb_map_retains_sorting (hb_first)
|
||||
;
|
||||
|
||||
coverage.serialize_serialize (c, glyphs);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
hb_set_t intersection;
|
||||
(this+coverage).intersect_set (glyphset, intersection);
|
||||
|
||||
unsigned new_format = valueFormat;
|
||||
|
||||
if (c->plan->normalized_coords)
|
||||
{
|
||||
new_format = valueFormat.get_effective_format (values.arrayZ, false, false, this, &c->plan->layout_variation_idx_delta_map);
|
||||
}
|
||||
/* do not strip hints for VF */
|
||||
else if (c->plan->flags & HB_SUBSET_FLAGS_NO_HINTING)
|
||||
{
|
||||
hb_blob_t* blob = hb_face_reference_table (c->plan->source, HB_TAG ('f','v','a','r'));
|
||||
bool has_fvar = (blob != hb_blob_get_empty ());
|
||||
hb_blob_destroy (blob);
|
||||
|
||||
bool strip = !has_fvar;
|
||||
/* special case: strip hints when a VF has no GDEF varstore after
|
||||
* subsetting*/
|
||||
if (has_fvar && !c->plan->has_gdef_varstore)
|
||||
strip = true;
|
||||
new_format = valueFormat.get_effective_format (values.arrayZ,
|
||||
strip, /* strip hints */
|
||||
true, /* strip empty */
|
||||
this, nullptr);
|
||||
}
|
||||
|
||||
auto it =
|
||||
+ hb_iter (intersection)
|
||||
| hb_map_retains_sorting (glyph_map)
|
||||
| hb_zip (hb_repeat (values.as_array (valueFormat.get_len ())))
|
||||
;
|
||||
|
||||
bool ret = bool (it);
|
||||
SinglePos_serialize (c->serializer, this, it, &c->plan->layout_variation_idx_delta_map, new_format);
|
||||
return_trace (ret);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_SINGLEPOSFORMAT1_HH */
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
#ifndef OT_LAYOUT_GPOS_SINGLEPOSFORMAT2_HH
|
||||
#define OT_LAYOUT_GPOS_SINGLEPOSFORMAT2_HH
|
||||
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
struct SinglePosFormat2 : ValueBase
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 2 */
|
||||
Offset16To<Coverage>
|
||||
coverage; /* Offset to Coverage table--from
|
||||
* beginning of subtable */
|
||||
ValueFormat valueFormat; /* Defines the types of data in the
|
||||
* ValueRecord */
|
||||
HBUINT16 valueCount; /* Number of ValueRecords */
|
||||
ValueRecord values; /* Array of ValueRecords--positioning
|
||||
* values applied to glyphs */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (8, values);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this) &&
|
||||
coverage.sanitize (c, this) &&
|
||||
valueFormat.sanitize_values (c, this, values, valueCount));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{ return (this+coverage).intersects (glyphs); }
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c) const
|
||||
{
|
||||
if (!valueFormat.has_device ()) return;
|
||||
|
||||
auto it =
|
||||
+ hb_zip (this+coverage, hb_range ((unsigned) valueCount))
|
||||
| hb_filter (c->glyph_set, hb_first)
|
||||
;
|
||||
|
||||
if (!it) return;
|
||||
|
||||
unsigned sub_length = valueFormat.get_len ();
|
||||
const hb_array_t<const Value> values_array = values.as_array (valueCount * sub_length);
|
||||
|
||||
for (unsigned i : + it
|
||||
| hb_map (hb_second))
|
||||
valueFormat.collect_variation_indices (c, this, values_array.sub_array (i * sub_length, sub_length));
|
||||
|
||||
}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{ if (unlikely (!(this+coverage).collect_coverage (c->input))) return; }
|
||||
|
||||
const Coverage &get_coverage () const { return this+coverage; }
|
||||
|
||||
ValueFormat get_value_format () const { return valueFormat; }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
hb_buffer_t *buffer = c->buffer;
|
||||
unsigned int index = (this+coverage).get_coverage (buffer->cur().codepoint);
|
||||
if (likely (index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
if (unlikely (index >= valueCount)) return_trace (false);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"positioning glyph at %u",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
valueFormat.apply_value (c, this,
|
||||
&values[index * valueFormat.get_len ()],
|
||||
buffer->cur_pos());
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"positioned glyph at %u",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
buffer->idx++;
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool
|
||||
position_single (hb_font_t *font,
|
||||
hb_blob_t *table_blob,
|
||||
hb_direction_t direction,
|
||||
hb_codepoint_t gid,
|
||||
hb_glyph_position_t &pos) const
|
||||
{
|
||||
unsigned int index = (this+coverage).get_coverage (gid);
|
||||
if (likely (index == NOT_COVERED)) return false;
|
||||
if (unlikely (index >= valueCount)) return false;
|
||||
|
||||
/* This is ugly... */
|
||||
hb_buffer_t buffer;
|
||||
buffer.props.direction = direction;
|
||||
OT::hb_ot_apply_context_t c (1, font, &buffer, table_blob);
|
||||
|
||||
valueFormat.apply_value (&c, this,
|
||||
&values[index * valueFormat.get_len ()],
|
||||
pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template<typename Iterator,
|
||||
typename SrcLookup,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
void serialize (hb_serialize_context_t *c,
|
||||
const SrcLookup *src,
|
||||
Iterator it,
|
||||
ValueFormat newFormat,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map)
|
||||
{
|
||||
auto out = c->extend_min (this);
|
||||
if (unlikely (!out)) return;
|
||||
if (unlikely (!c->check_assign (valueFormat, newFormat, HB_SERIALIZE_ERROR_INT_OVERFLOW))) return;
|
||||
if (unlikely (!c->check_assign (valueCount, it.len (), HB_SERIALIZE_ERROR_ARRAY_OVERFLOW))) return;
|
||||
|
||||
+ it
|
||||
| hb_map (hb_second)
|
||||
| hb_apply ([&] (hb_array_t<const Value> _)
|
||||
{ src->get_value_format ().copy_values (c, newFormat, src, &_, layout_variation_idx_delta_map); })
|
||||
;
|
||||
|
||||
auto glyphs =
|
||||
+ it
|
||||
| hb_map_retains_sorting (hb_first)
|
||||
;
|
||||
|
||||
coverage.serialize_serialize (c, glyphs);
|
||||
}
|
||||
|
||||
template<typename Iterator,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
unsigned compute_effective_format (const hb_face_t *face,
|
||||
Iterator it,
|
||||
bool is_instancing, bool strip_hints,
|
||||
bool has_gdef_varstore,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map) const
|
||||
{
|
||||
hb_blob_t* blob = hb_face_reference_table (face, HB_TAG ('f','v','a','r'));
|
||||
bool has_fvar = (blob != hb_blob_get_empty ());
|
||||
hb_blob_destroy (blob);
|
||||
|
||||
unsigned new_format = 0;
|
||||
if (is_instancing)
|
||||
{
|
||||
new_format = new_format | valueFormat.get_effective_format (+ it | hb_map (hb_second), false, false, this, varidx_delta_map);
|
||||
}
|
||||
/* do not strip hints for VF */
|
||||
else if (strip_hints)
|
||||
{
|
||||
bool strip = !has_fvar;
|
||||
if (has_fvar && !has_gdef_varstore)
|
||||
strip = true;
|
||||
new_format = new_format | valueFormat.get_effective_format (+ it | hb_map (hb_second), strip, true, this, nullptr);
|
||||
}
|
||||
else
|
||||
new_format = valueFormat;
|
||||
|
||||
return new_format;
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
unsigned sub_length = valueFormat.get_len ();
|
||||
auto values_array = values.as_array (valueCount * sub_length);
|
||||
|
||||
auto it =
|
||||
+ hb_zip (this+coverage, hb_range ((unsigned) valueCount))
|
||||
| hb_filter (glyphset, hb_first)
|
||||
| hb_map_retains_sorting ([&] (const hb_pair_t<hb_codepoint_t, unsigned>& _)
|
||||
{
|
||||
return hb_pair (glyph_map[_.first],
|
||||
values_array.sub_array (_.second * sub_length,
|
||||
sub_length));
|
||||
})
|
||||
;
|
||||
|
||||
unsigned new_format = compute_effective_format (c->plan->source, it,
|
||||
bool (c->plan->normalized_coords),
|
||||
bool (c->plan->flags & HB_SUBSET_FLAGS_NO_HINTING),
|
||||
c->plan->has_gdef_varstore,
|
||||
&c->plan->layout_variation_idx_delta_map);
|
||||
bool ret = bool (it);
|
||||
SinglePos_serialize (c->serializer, this, it, &c->plan->layout_variation_idx_delta_map, new_format);
|
||||
return_trace (ret);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GPOS_SINGLEPOSFORMAT2_HH */
|
||||
+441
@@ -0,0 +1,441 @@
|
||||
#ifndef OT_LAYOUT_GPOS_VALUEFORMAT_HH
|
||||
#define OT_LAYOUT_GPOS_VALUEFORMAT_HH
|
||||
|
||||
#include "../../../hb-ot-layout-gsubgpos.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GPOS_impl {
|
||||
|
||||
typedef HBUINT16 Value;
|
||||
|
||||
struct ValueBase {}; // Dummy base class tag for OffsetTo<Value> bases.
|
||||
|
||||
typedef UnsizedArrayOf<Value> ValueRecord;
|
||||
|
||||
struct ValueFormat : HBUINT16
|
||||
{
|
||||
enum Flags {
|
||||
xPlacement = 0x0001u, /* Includes horizontal adjustment for placement */
|
||||
yPlacement = 0x0002u, /* Includes vertical adjustment for placement */
|
||||
xAdvance = 0x0004u, /* Includes horizontal adjustment for advance */
|
||||
yAdvance = 0x0008u, /* Includes vertical adjustment for advance */
|
||||
xPlaDevice = 0x0010u, /* Includes horizontal Device table for placement */
|
||||
yPlaDevice = 0x0020u, /* Includes vertical Device table for placement */
|
||||
xAdvDevice = 0x0040u, /* Includes horizontal Device table for advance */
|
||||
yAdvDevice = 0x0080u, /* Includes vertical Device table for advance */
|
||||
ignored = 0x0F00u, /* Was used in TrueType Open for MM fonts */
|
||||
reserved = 0xF000u, /* For future use */
|
||||
|
||||
devices = 0x00F0u /* Mask for having any Device table */
|
||||
};
|
||||
|
||||
/* All fields are options. Only those available advance the value pointer. */
|
||||
#if 0
|
||||
HBINT16 xPlacement; /* Horizontal adjustment for
|
||||
* placement--in design units */
|
||||
HBINT16 yPlacement; /* Vertical adjustment for
|
||||
* placement--in design units */
|
||||
HBINT16 xAdvance; /* Horizontal adjustment for
|
||||
* advance--in design units (only used
|
||||
* for horizontal writing) */
|
||||
HBINT16 yAdvance; /* Vertical adjustment for advance--in
|
||||
* design units (only used for vertical
|
||||
* writing) */
|
||||
Offset16To<Device> xPlaDevice; /* Offset to Device table for
|
||||
* horizontal placement--measured from
|
||||
* beginning of PosTable (may be NULL) */
|
||||
Offset16To<Device> yPlaDevice; /* Offset to Device table for vertical
|
||||
* placement--measured from beginning
|
||||
* of PosTable (may be NULL) */
|
||||
Offset16To<Device> xAdvDevice; /* Offset to Device table for
|
||||
* horizontal advance--measured from
|
||||
* beginning of PosTable (may be NULL) */
|
||||
Offset16To<Device> yAdvDevice; /* Offset to Device table for vertical
|
||||
* advance--measured from beginning of
|
||||
* PosTable (may be NULL) */
|
||||
#endif
|
||||
|
||||
IntType& operator = (uint16_t i) { v = i; return *this; }
|
||||
|
||||
unsigned int get_len () const { return hb_popcount ((unsigned int) *this); }
|
||||
unsigned int get_size () const { return get_len () * Value::static_size; }
|
||||
|
||||
hb_vector_t<unsigned> get_device_table_indices () const {
|
||||
unsigned i = 0;
|
||||
hb_vector_t<unsigned> result;
|
||||
unsigned format = *this;
|
||||
|
||||
if (format & xPlacement) i++;
|
||||
if (format & yPlacement) i++;
|
||||
if (format & xAdvance) i++;
|
||||
if (format & yAdvance) i++;
|
||||
|
||||
if (format & xPlaDevice) result.push (i++);
|
||||
if (format & yPlaDevice) result.push (i++);
|
||||
if (format & xAdvDevice) result.push (i++);
|
||||
if (format & yAdvDevice) result.push (i++);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool apply_value (hb_ot_apply_context_t *c,
|
||||
const ValueBase *base,
|
||||
const Value *values,
|
||||
hb_glyph_position_t &glyph_pos) const
|
||||
{
|
||||
bool ret = false;
|
||||
unsigned int format = *this;
|
||||
if (!format) return ret;
|
||||
|
||||
hb_font_t *font = c->font;
|
||||
bool horizontal =
|
||||
#ifndef HB_NO_VERTICAL
|
||||
HB_DIRECTION_IS_HORIZONTAL (c->direction)
|
||||
#else
|
||||
true
|
||||
#endif
|
||||
;
|
||||
|
||||
if (format & xPlacement) glyph_pos.x_offset += font->em_scale_x (get_short (values++, &ret));
|
||||
if (format & yPlacement) glyph_pos.y_offset += font->em_scale_y (get_short (values++, &ret));
|
||||
if (format & xAdvance) {
|
||||
if (likely (horizontal)) glyph_pos.x_advance += font->em_scale_x (get_short (values, &ret));
|
||||
values++;
|
||||
}
|
||||
/* y_advance values grow downward but font-space grows upward, hence negation */
|
||||
if (format & yAdvance) {
|
||||
if (unlikely (!horizontal)) glyph_pos.y_advance -= font->em_scale_y (get_short (values, &ret));
|
||||
values++;
|
||||
}
|
||||
|
||||
if (!has_device ()) return ret;
|
||||
|
||||
bool use_x_device = font->x_ppem || font->num_coords;
|
||||
bool use_y_device = font->y_ppem || font->num_coords;
|
||||
|
||||
if (!use_x_device && !use_y_device) return ret;
|
||||
|
||||
const ItemVariationStore &store = c->var_store;
|
||||
auto *cache = c->var_store_cache;
|
||||
|
||||
/* pixel -> fractional pixel */
|
||||
if (format & xPlaDevice)
|
||||
{
|
||||
if (use_x_device) glyph_pos.x_offset += get_device (values, &ret, base, c->sanitizer).get_x_delta (font, store, cache);
|
||||
values++;
|
||||
}
|
||||
if (format & yPlaDevice)
|
||||
{
|
||||
if (use_y_device) glyph_pos.y_offset += get_device (values, &ret, base, c->sanitizer).get_y_delta (font, store, cache);
|
||||
values++;
|
||||
}
|
||||
if (format & xAdvDevice)
|
||||
{
|
||||
if (horizontal && use_x_device) glyph_pos.x_advance += get_device (values, &ret, base, c->sanitizer).get_x_delta (font, store, cache);
|
||||
values++;
|
||||
}
|
||||
if (format & yAdvDevice)
|
||||
{
|
||||
/* y_advance values grow downward but font-space grows upward, hence negation */
|
||||
if (!horizontal && use_y_device) glyph_pos.y_advance -= get_device (values, &ret, base, c->sanitizer).get_y_delta (font, store, cache);
|
||||
values++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsigned int get_effective_format (const Value *values, bool strip_hints, bool strip_empty, const ValueBase *base,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map) const
|
||||
{
|
||||
unsigned int format = *this;
|
||||
for (unsigned flag = xPlacement; flag <= yAdvDevice; flag = flag << 1) {
|
||||
if (format & flag)
|
||||
{
|
||||
if (strip_hints && flag >= xPlaDevice)
|
||||
{
|
||||
format = format & ~flag;
|
||||
values++;
|
||||
continue;
|
||||
}
|
||||
if (varidx_delta_map && flag >= xPlaDevice)
|
||||
{
|
||||
update_var_flag (values++, (Flags) flag, &format, base, varidx_delta_map);
|
||||
continue;
|
||||
}
|
||||
/* do not strip empty when instancing, cause we don't know whether the new
|
||||
* default value is 0 or not */
|
||||
if (strip_empty) should_drop (*values, (Flags) flag, &format);
|
||||
values++;
|
||||
}
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
template<typename Iterator,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
unsigned int get_effective_format (Iterator it, bool strip_hints, bool strip_empty, const ValueBase *base,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map) const {
|
||||
unsigned int new_format = 0;
|
||||
|
||||
for (const hb_array_t<const Value>& values : it)
|
||||
new_format = new_format | get_effective_format (&values, strip_hints, strip_empty, base, varidx_delta_map);
|
||||
|
||||
return new_format;
|
||||
}
|
||||
|
||||
void copy_values (hb_serialize_context_t *c,
|
||||
unsigned int new_format,
|
||||
const ValueBase *base,
|
||||
const Value *values,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map) const
|
||||
{
|
||||
unsigned int format = *this;
|
||||
if (!format) return;
|
||||
|
||||
HBINT16 *x_placement = nullptr, *y_placement = nullptr, *x_adv = nullptr, *y_adv = nullptr;
|
||||
if (format & xPlacement) x_placement = copy_value (c, new_format, xPlacement, *values++);
|
||||
if (format & yPlacement) y_placement = copy_value (c, new_format, yPlacement, *values++);
|
||||
if (format & xAdvance) x_adv = copy_value (c, new_format, xAdvance, *values++);
|
||||
if (format & yAdvance) y_adv = copy_value (c, new_format, yAdvance, *values++);
|
||||
|
||||
if (!has_device ())
|
||||
return;
|
||||
|
||||
if (format & xPlaDevice)
|
||||
{
|
||||
add_delta_to_value (x_placement, base, values, layout_variation_idx_delta_map);
|
||||
copy_device (c, base, values++, layout_variation_idx_delta_map, new_format, xPlaDevice);
|
||||
}
|
||||
|
||||
if (format & yPlaDevice)
|
||||
{
|
||||
add_delta_to_value (y_placement, base, values, layout_variation_idx_delta_map);
|
||||
copy_device (c, base, values++, layout_variation_idx_delta_map, new_format, yPlaDevice);
|
||||
}
|
||||
|
||||
if (format & xAdvDevice)
|
||||
{
|
||||
add_delta_to_value (x_adv, base, values, layout_variation_idx_delta_map);
|
||||
copy_device (c, base, values++, layout_variation_idx_delta_map, new_format, xAdvDevice);
|
||||
}
|
||||
|
||||
if (format & yAdvDevice)
|
||||
{
|
||||
add_delta_to_value (y_adv, base, values, layout_variation_idx_delta_map);
|
||||
copy_device (c, base, values++, layout_variation_idx_delta_map, new_format, yAdvDevice);
|
||||
}
|
||||
}
|
||||
|
||||
HBINT16* copy_value (hb_serialize_context_t *c,
|
||||
unsigned int new_format,
|
||||
Flags flag,
|
||||
Value value) const
|
||||
{
|
||||
// Filter by new format.
|
||||
if (!(new_format & flag)) return nullptr;
|
||||
return reinterpret_cast<HBINT16 *> (c->copy (value));
|
||||
}
|
||||
|
||||
void collect_variation_indices (hb_collect_variation_indices_context_t *c,
|
||||
const ValueBase *base,
|
||||
const hb_array_t<const Value>& values) const
|
||||
{
|
||||
unsigned format = *this;
|
||||
unsigned i = 0;
|
||||
if (format & xPlacement) i++;
|
||||
if (format & yPlacement) i++;
|
||||
if (format & xAdvance) i++;
|
||||
if (format & yAdvance) i++;
|
||||
if (format & xPlaDevice)
|
||||
{
|
||||
(base + get_device (&(values[i]))).collect_variation_indices (c);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (format & ValueFormat::yPlaDevice)
|
||||
{
|
||||
(base + get_device (&(values[i]))).collect_variation_indices (c);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (format & ValueFormat::xAdvDevice)
|
||||
{
|
||||
(base + get_device (&(values[i]))).collect_variation_indices (c);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (format & ValueFormat::yAdvDevice)
|
||||
{
|
||||
(base + get_device (&(values[i]))).collect_variation_indices (c);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool sanitize_value_devices (hb_sanitize_context_t *c, const ValueBase *base, const Value *values) const
|
||||
{
|
||||
unsigned int format = *this;
|
||||
|
||||
if (format & xPlacement) values++;
|
||||
if (format & yPlacement) values++;
|
||||
if (format & xAdvance) values++;
|
||||
if (format & yAdvance) values++;
|
||||
|
||||
if ((format & xPlaDevice) && !get_device (values++).sanitize (c, base)) return false;
|
||||
if ((format & yPlaDevice) && !get_device (values++).sanitize (c, base)) return false;
|
||||
if ((format & xAdvDevice) && !get_device (values++).sanitize (c, base)) return false;
|
||||
if ((format & yAdvDevice) && !get_device (values++).sanitize (c, base)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline Offset16To<Device, ValueBase>& get_device (Value* value)
|
||||
{
|
||||
return *static_cast<Offset16To<Device, ValueBase> *> (value);
|
||||
}
|
||||
static inline const Offset16To<Device, ValueBase>& get_device (const Value* value)
|
||||
{
|
||||
return *static_cast<const Offset16To<Device, ValueBase> *> (value);
|
||||
}
|
||||
static inline const Device& get_device (const Value* value,
|
||||
bool *worked,
|
||||
const ValueBase *base,
|
||||
hb_sanitize_context_t &c)
|
||||
{
|
||||
if (worked) *worked |= bool (*value);
|
||||
auto &offset = *static_cast<const Offset16To<Device> *> (value);
|
||||
|
||||
if (unlikely (!offset.sanitize (&c, base)))
|
||||
return Null(Device);
|
||||
hb_barrier ();
|
||||
|
||||
return base + offset;
|
||||
}
|
||||
|
||||
void add_delta_to_value (HBINT16 *value,
|
||||
const ValueBase *base,
|
||||
const Value *src_value,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map) const
|
||||
{
|
||||
if (!value) return;
|
||||
unsigned varidx = (base + get_device (src_value)).get_variation_index ();
|
||||
hb_pair_t<unsigned, int> *varidx_delta;
|
||||
if (!layout_variation_idx_delta_map->has (varidx, &varidx_delta)) return;
|
||||
|
||||
*value += hb_second (*varidx_delta);
|
||||
}
|
||||
|
||||
bool copy_device (hb_serialize_context_t *c,
|
||||
const ValueBase *base,
|
||||
const Value *src_value,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *layout_variation_idx_delta_map,
|
||||
unsigned int new_format, Flags flag) const
|
||||
{
|
||||
// Filter by new format.
|
||||
if (!(new_format & flag)) return true;
|
||||
|
||||
Value *dst_value = c->copy (*src_value);
|
||||
|
||||
if (!dst_value) return false;
|
||||
if (*dst_value == 0) return true;
|
||||
|
||||
*dst_value = 0;
|
||||
c->push ();
|
||||
if ((base + get_device (src_value)).copy (c, layout_variation_idx_delta_map))
|
||||
{
|
||||
c->add_link (*dst_value, c->pop_pack ());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
c->pop_discard ();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline const HBINT16& get_short (const Value* value, bool *worked=nullptr)
|
||||
{
|
||||
if (worked) *worked |= bool (*value);
|
||||
return *reinterpret_cast<const HBINT16 *> (value);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool has_device () const
|
||||
{
|
||||
unsigned int format = *this;
|
||||
return (format & devices) != 0;
|
||||
}
|
||||
|
||||
bool sanitize_value (hb_sanitize_context_t *c, const ValueBase *base, const Value *values) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
|
||||
if (unlikely (!c->check_range (values, get_size ()))) return_trace (false);
|
||||
|
||||
if (c->lazy_some_gpos)
|
||||
return_trace (true);
|
||||
|
||||
return_trace (!has_device () || sanitize_value_devices (c, base, values));
|
||||
}
|
||||
|
||||
bool sanitize_values (hb_sanitize_context_t *c, const ValueBase *base, const Value *values, unsigned int count) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
unsigned size = get_size ();
|
||||
|
||||
if (!c->check_range (values, count, size)) return_trace (false);
|
||||
|
||||
if (c->lazy_some_gpos)
|
||||
return_trace (true);
|
||||
|
||||
hb_barrier ();
|
||||
return_trace (sanitize_values_stride_unsafe (c, base, values, count, size));
|
||||
}
|
||||
|
||||
/* Just sanitize referenced Device tables. Doesn't check the values themselves. */
|
||||
bool sanitize_values_stride_unsafe (hb_sanitize_context_t *c, const ValueBase *base, const Value *values, unsigned int count, unsigned int stride) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
|
||||
if (!has_device ()) return_trace (true);
|
||||
|
||||
for (unsigned int i = 0; i < count; i++) {
|
||||
if (!sanitize_value_devices (c, base, values))
|
||||
return_trace (false);
|
||||
values = &StructAtOffset<const Value> (values, stride);
|
||||
}
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void should_drop (Value value, Flags flag, unsigned int* format) const
|
||||
{
|
||||
if (value) return;
|
||||
*format = *format & ~flag;
|
||||
}
|
||||
|
||||
void update_var_flag (const Value* value, Flags flag,
|
||||
unsigned int* format, const ValueBase *base,
|
||||
const hb_hashmap_t<unsigned, hb_pair_t<unsigned, int>> *varidx_delta_map) const
|
||||
{
|
||||
if (*value)
|
||||
{
|
||||
unsigned varidx = (base + get_device (value)).get_variation_index ();
|
||||
hb_pair_t<unsigned, int> *varidx_delta;
|
||||
if (varidx_delta_map->has (varidx, &varidx_delta) &&
|
||||
varidx_delta->first != HB_OT_LAYOUT_NO_VARIATIONS_INDEX)
|
||||
return;
|
||||
}
|
||||
*format = *format & ~flag;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // #ifndef OT_LAYOUT_GPOS_VALUEFORMAT_HH
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
#ifndef OT_LAYOUT_GSUB_ALTERNATESET_HH
|
||||
#define OT_LAYOUT_GSUB_ALTERNATESET_HH
|
||||
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
template <typename Types>
|
||||
struct AlternateSet
|
||||
{
|
||||
protected:
|
||||
Array16Of<typename Types::HBGlyphID>
|
||||
alternates; /* Array of alternate GlyphIDs--in
|
||||
* arbitrary order */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (2, alternates);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (alternates.sanitize (c));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{ return hb_any (alternates, glyphs); }
|
||||
|
||||
void closure (hb_closure_context_t *c) const
|
||||
{ c->output->add_array (alternates.arrayZ, alternates.len); }
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{ c->output->add_array (alternates.arrayZ, alternates.len); }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
unsigned int count = alternates.len;
|
||||
|
||||
if (unlikely (!count)) return_trace (false);
|
||||
|
||||
hb_mask_t glyph_mask = c->buffer->cur().mask;
|
||||
hb_mask_t lookup_mask = c->lookup_mask;
|
||||
|
||||
/* Note: This breaks badly if two features enabled this lookup together. */
|
||||
unsigned int shift = hb_ctz (lookup_mask);
|
||||
unsigned int alt_index = ((lookup_mask & glyph_mask) >> shift);
|
||||
|
||||
/* If alt_index is MAX_VALUE, randomize feature if it is the rand feature. */
|
||||
if (alt_index == HB_OT_MAP_MAX_VALUE && c->random)
|
||||
{
|
||||
/* Maybe we can do better than unsafe-to-break all; but since we are
|
||||
* changing random state, it would be hard to track that. Good 'nough. */
|
||||
c->buffer->unsafe_to_break (0, c->buffer->len);
|
||||
alt_index = c->random_number () % count + 1;
|
||||
}
|
||||
|
||||
if (unlikely (alt_index > count || alt_index == 0)) return_trace (false);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->sync_so_far ();
|
||||
c->buffer->message (c->font,
|
||||
"replacing glyph at %u (alternate substitution)",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
c->replace_glyph (alternates[alt_index - 1]);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"replaced glyph at %u (alternate substitution)",
|
||||
c->buffer->idx - 1u);
|
||||
}
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
unsigned
|
||||
get_alternates (unsigned start_offset,
|
||||
unsigned *alternate_count /* IN/OUT. May be NULL. */,
|
||||
hb_codepoint_t *alternate_glyphs /* OUT. May be NULL. */) const
|
||||
{
|
||||
if (alternates.len && alternate_count)
|
||||
{
|
||||
+ alternates.as_array ().sub_array (start_offset, alternate_count)
|
||||
| hb_sink (hb_array (alternate_glyphs, *alternate_count))
|
||||
;
|
||||
}
|
||||
return alternates.len;
|
||||
}
|
||||
|
||||
template <typename Iterator,
|
||||
hb_requires (hb_is_source_of (Iterator, hb_codepoint_t))>
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
Iterator alts)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
return_trace (alternates.serialize (c, alts));
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
auto it =
|
||||
+ hb_iter (alternates)
|
||||
| hb_filter (glyphset)
|
||||
| hb_map (glyph_map)
|
||||
;
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
return_trace (out->serialize (c->serializer, it) &&
|
||||
out->alternates);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_ALTERNATESET_HH */
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
#ifndef OT_LAYOUT_GSUB_ALTERNATESUBST_HH
|
||||
#define OT_LAYOUT_GSUB_ALTERNATESUBST_HH
|
||||
|
||||
#include "AlternateSubstFormat1.hh"
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
struct AlternateSubst
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
AlternateSubstFormat1_2<SmallTypes> format1;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
AlternateSubstFormat1_2<MediumTypes> format2;
|
||||
#endif
|
||||
} u;
|
||||
public:
|
||||
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{
|
||||
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
|
||||
TRACE_DISPATCH (this, u.format);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
|
||||
#endif
|
||||
default:return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO This function is unused and not updated to 24bit GIDs. Should be done by using
|
||||
* iterators. While at it perhaps using iterator of arrays of hb_codepoint_t instead. */
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
hb_sorted_array_t<const HBGlyphID16> glyphs,
|
||||
hb_array_t<const unsigned int> alternate_len_list,
|
||||
hb_array_t<const HBGlyphID16> alternate_glyphs_list)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!c->extend_min (u.format))) return_trace (false);
|
||||
unsigned int format = 1;
|
||||
u.format = format;
|
||||
switch (u.format) {
|
||||
case 1: return_trace (u.format1.serialize (c, glyphs, alternate_len_list, alternate_glyphs_list));
|
||||
default:return_trace (false);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO subset() should choose format. */
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_ALTERNATESUBST_HH */
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
#ifndef OT_LAYOUT_GSUB_ALTERNATESUBSTFORMAT1_HH
|
||||
#define OT_LAYOUT_GSUB_ALTERNATESUBSTFORMAT1_HH
|
||||
|
||||
#include "AlternateSet.hh"
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
template <typename Types>
|
||||
struct AlternateSubstFormat1_2
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
coverage; /* Offset to Coverage table--from
|
||||
* beginning of Substitution table */
|
||||
Array16Of<typename Types::template OffsetTo<AlternateSet<Types>>>
|
||||
alternateSet; /* Array of AlternateSet tables
|
||||
* ordered by Coverage Index */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (2 + 2 * Types::size, alternateSet);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (coverage.sanitize (c, this) && alternateSet.sanitize (c, this));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{ return (this+coverage).intersects (glyphs); }
|
||||
|
||||
bool may_have_non_1to1 () const
|
||||
{ return false; }
|
||||
|
||||
void closure (hb_closure_context_t *c) const
|
||||
{
|
||||
+ hb_zip (this+coverage, alternateSet)
|
||||
| hb_filter (c->parent_active_glyphs (), hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_map (hb_add (this))
|
||||
| hb_apply ([c] (const AlternateSet<Types> &_) { _.closure (c); })
|
||||
;
|
||||
}
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
|
||||
+ hb_zip (this+coverage, alternateSet)
|
||||
| hb_map (hb_second)
|
||||
| hb_map (hb_add (this))
|
||||
| hb_apply ([c] (const AlternateSet<Types> &_) { _.collect_glyphs (c); })
|
||||
;
|
||||
}
|
||||
|
||||
const Coverage &get_coverage () const { return this+coverage; }
|
||||
|
||||
bool would_apply (hb_would_apply_context_t *c) const
|
||||
{ return c->len == 1 && (this+coverage).get_coverage (c->glyphs[0]) != NOT_COVERED; }
|
||||
|
||||
unsigned
|
||||
get_glyph_alternates (hb_codepoint_t gid,
|
||||
unsigned start_offset,
|
||||
unsigned *alternate_count /* IN/OUT. May be NULL. */,
|
||||
hb_codepoint_t *alternate_glyphs /* OUT. May be NULL. */) const
|
||||
{ return (this+alternateSet[(this+coverage).get_coverage (gid)])
|
||||
.get_alternates (start_offset, alternate_count, alternate_glyphs); }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
|
||||
unsigned int index = (this+coverage).get_coverage (c->buffer->cur().codepoint);
|
||||
if (likely (index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
return_trace ((this+alternateSet[index]).apply (c));
|
||||
}
|
||||
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
hb_sorted_array_t<const HBGlyphID16> glyphs,
|
||||
hb_array_t<const unsigned int> alternate_len_list,
|
||||
hb_array_t<const HBGlyphID16> alternate_glyphs_list)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!c->extend_min (this))) return_trace (false);
|
||||
if (unlikely (!alternateSet.serialize (c, glyphs.length))) return_trace (false);
|
||||
for (unsigned int i = 0; i < glyphs.length; i++)
|
||||
{
|
||||
unsigned int alternate_len = alternate_len_list[i];
|
||||
if (unlikely (!alternateSet[i]
|
||||
.serialize_serialize (c, alternate_glyphs_list.sub_array (0, alternate_len))))
|
||||
return_trace (false);
|
||||
alternate_glyphs_list += alternate_len;
|
||||
}
|
||||
return_trace (coverage.serialize_serialize (c, glyphs));
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
out->format = format;
|
||||
|
||||
hb_sorted_vector_t<hb_codepoint_t> new_coverage;
|
||||
+ hb_zip (this+coverage, alternateSet)
|
||||
| hb_filter (glyphset, hb_first)
|
||||
| hb_filter (subset_offset_array (c, out->alternateSet, this), hb_second)
|
||||
| hb_map (hb_first)
|
||||
| hb_map (glyph_map)
|
||||
| hb_sink (new_coverage)
|
||||
;
|
||||
out->coverage.serialize_serialize (c->serializer, new_coverage.iter ());
|
||||
return_trace (bool (new_coverage));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_ALTERNATESUBSTFORMAT1_HH */
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#ifndef OT_LAYOUT_GSUB_CHAINCONTEXTSUBST_HH
|
||||
#define OT_LAYOUT_GSUB_CHAINCONTEXTSUBST_HH
|
||||
|
||||
// TODO(garretrieger): move to new layout.
|
||||
#include "../../../hb-ot-layout-gsubgpos.hh"
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
struct ChainContextSubst : ChainContext {};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_CHAINCONTEXTSUBST_HH */
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef OT_LAYOUT_GSUB_COMMON_HH
|
||||
#define OT_LAYOUT_GSUB_COMMON_HH
|
||||
|
||||
#include "../../../hb-serialize.hh"
|
||||
#include "../../../hb-ot-layout-gsubgpos.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
template<typename Iterator>
|
||||
static void SingleSubst_serialize (hb_serialize_context_t *c,
|
||||
Iterator it);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_COMMON_HH */
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#ifndef OT_LAYOUT_GSUB_CONTEXTSUBST_HH
|
||||
#define OT_LAYOUT_GSUB_CONTEXTSUBST_HH
|
||||
|
||||
// TODO(garretrieger): move to new layout.
|
||||
#include "../../../hb-ot-layout-gsubgpos.hh"
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
struct ContextSubst : Context {};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_CONTEXTSUBST_HH */
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#ifndef OT_LAYOUT_GSUB_EXTENSIONSUBST_HH
|
||||
#define OT_LAYOUT_GSUB_EXTENSIONSUBST_HH
|
||||
|
||||
// TODO(garretrieger): move to new layout.
|
||||
#include "../../../hb-ot-layout-gsubgpos.hh"
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
struct ExtensionSubst : Extension<ExtensionSubst>
|
||||
{
|
||||
typedef struct SubstLookupSubTable SubTable;
|
||||
bool is_reverse () const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_EXTENSIONSUBST_HH */
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef OT_LAYOUT_GSUB_GSUB_HH
|
||||
#define OT_LAYOUT_GSUB_GSUB_HH
|
||||
|
||||
#include "../../../hb-ot-layout-gsubgpos.hh"
|
||||
#include "Common.hh"
|
||||
#include "SubstLookup.hh"
|
||||
|
||||
namespace OT {
|
||||
|
||||
using Layout::GSUB_impl::SubstLookup;
|
||||
|
||||
namespace Layout {
|
||||
|
||||
/*
|
||||
* GSUB -- Glyph Substitution
|
||||
* https://docs.microsoft.com/en-us/typography/opentype/spec/gsub
|
||||
*/
|
||||
|
||||
struct GSUB : GSUBGPOS
|
||||
{
|
||||
using Lookup = SubstLookup;
|
||||
|
||||
static constexpr hb_tag_t tableTag = HB_OT_TAG_GSUB;
|
||||
|
||||
const SubstLookup& get_lookup (unsigned int i) const
|
||||
{ return static_cast<const SubstLookup &> (GSUBGPOS::get_lookup (i)); }
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
hb_subset_layout_context_t l (c, tableTag);
|
||||
return GSUBGPOS::subset<SubstLookup> (&l);
|
||||
}
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (GSUBGPOS::sanitize<SubstLookup> (c));
|
||||
}
|
||||
|
||||
HB_INTERNAL bool is_blocklisted (hb_blob_t *blob,
|
||||
hb_face_t *face) const;
|
||||
|
||||
void closure_lookups (hb_face_t *face,
|
||||
const hb_set_t *glyphs,
|
||||
hb_set_t *lookup_indexes /* IN/OUT */) const
|
||||
{ GSUBGPOS::closure_lookups<SubstLookup> (face, glyphs, lookup_indexes); }
|
||||
|
||||
typedef GSUBGPOS::accelerator_t<GSUB> accelerator_t;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
struct GSUB_accelerator_t : Layout::GSUB::accelerator_t {
|
||||
GSUB_accelerator_t (hb_face_t *face) : Layout::GSUB::accelerator_t (face) {}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_GSUB_HH */
|
||||
@@ -0,0 +1,203 @@
|
||||
#ifndef OT_LAYOUT_GSUB_LIGATURE_HH
|
||||
#define OT_LAYOUT_GSUB_LIGATURE_HH
|
||||
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
template <typename Types>
|
||||
struct Ligature
|
||||
{
|
||||
public:
|
||||
typename Types::HBGlyphID
|
||||
ligGlyph; /* GlyphID of ligature to substitute */
|
||||
HeadlessArray16Of<typename Types::HBGlyphID>
|
||||
component; /* Array of component GlyphIDs--start
|
||||
* with the second component--ordered
|
||||
* in writing direction */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (Types::size + 2, component);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (ligGlyph.sanitize (c) && component.sanitize (c));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{ return hb_all (component, glyphs); }
|
||||
|
||||
bool intersects_lig_glyph (const hb_set_t *glyphs) const
|
||||
{ return glyphs->has(ligGlyph); }
|
||||
|
||||
void closure (hb_closure_context_t *c) const
|
||||
{
|
||||
if (!intersects (c->glyphs)) return;
|
||||
c->output->add (ligGlyph);
|
||||
}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
c->input->add_array (component.arrayZ, component.get_length ());
|
||||
c->output->add (ligGlyph);
|
||||
}
|
||||
|
||||
bool would_apply (hb_would_apply_context_t *c) const
|
||||
{
|
||||
if (c->len != component.lenP1)
|
||||
return false;
|
||||
|
||||
for (unsigned int i = 1; i < c->len; i++)
|
||||
if (likely (c->glyphs[i] != component[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
unsigned int count = component.lenP1;
|
||||
|
||||
if (unlikely (!count)) return_trace (false);
|
||||
|
||||
/* Special-case to make it in-place and not consider this
|
||||
* as a "ligated" substitution. */
|
||||
if (unlikely (count == 1))
|
||||
{
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->sync_so_far ();
|
||||
c->buffer->message (c->font,
|
||||
"replacing glyph at %u (ligature substitution)",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
c->replace_glyph (ligGlyph);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"replaced glyph at %u (ligature substitution)",
|
||||
c->buffer->idx - 1u);
|
||||
}
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
unsigned int total_component_count = 0;
|
||||
|
||||
if (unlikely (count > HB_MAX_CONTEXT_LENGTH)) return false;
|
||||
unsigned match_positions_stack[4];
|
||||
unsigned *match_positions = match_positions_stack;
|
||||
if (unlikely (count > ARRAY_LENGTH (match_positions_stack)))
|
||||
{
|
||||
match_positions = (unsigned *) hb_malloc (hb_max (count, 1u) * sizeof (unsigned));
|
||||
if (unlikely (!match_positions))
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
unsigned int match_end = 0;
|
||||
|
||||
if (likely (!match_input (c, count,
|
||||
&component[1],
|
||||
match_glyph,
|
||||
nullptr,
|
||||
&match_end,
|
||||
match_positions,
|
||||
&total_component_count)))
|
||||
{
|
||||
c->buffer->unsafe_to_concat (c->buffer->idx, match_end);
|
||||
if (match_positions != match_positions_stack)
|
||||
hb_free (match_positions);
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
unsigned pos = 0;
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
unsigned delta = c->buffer->sync_so_far ();
|
||||
|
||||
pos = c->buffer->idx;
|
||||
|
||||
char buf[HB_MAX_CONTEXT_LENGTH * 16] = {0};
|
||||
char *p = buf;
|
||||
|
||||
match_end += delta;
|
||||
for (unsigned i = 0; i < count; i++)
|
||||
{
|
||||
match_positions[i] += delta;
|
||||
if (i)
|
||||
*p++ = ',';
|
||||
snprintf (p, sizeof(buf) - (p - buf), "%u", match_positions[i]);
|
||||
p += strlen(p);
|
||||
}
|
||||
|
||||
c->buffer->message (c->font,
|
||||
"ligating glyphs at %s",
|
||||
buf);
|
||||
}
|
||||
|
||||
ligate_input (c,
|
||||
count,
|
||||
match_positions,
|
||||
match_end,
|
||||
ligGlyph,
|
||||
total_component_count);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->sync_so_far ();
|
||||
c->buffer->message (c->font,
|
||||
"ligated glyph at %u",
|
||||
pos);
|
||||
}
|
||||
|
||||
if (match_positions != match_positions_stack)
|
||||
hb_free (match_positions);
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
template <typename Iterator,
|
||||
hb_requires (hb_is_source_of (Iterator, hb_codepoint_t))>
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
hb_codepoint_t ligature,
|
||||
Iterator components /* Starting from second */)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!c->extend_min (this))) return_trace (false);
|
||||
ligGlyph = ligature;
|
||||
if (unlikely (!component.serialize (c, components))) return_trace (false);
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c, unsigned coverage_idx) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
if (!intersects (&glyphset) || !glyphset.has (ligGlyph)) return_trace (false);
|
||||
// Ensure Coverage table is always packed after this.
|
||||
c->serializer->add_virtual_link (coverage_idx);
|
||||
|
||||
auto it =
|
||||
+ hb_iter (component)
|
||||
| hb_map (glyph_map)
|
||||
;
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
return_trace (out->serialize (c->serializer,
|
||||
glyph_map[ligGlyph],
|
||||
it)); }
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_LIGATURE_HH */
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
#ifndef OT_LAYOUT_GSUB_LIGATURESET_HH
|
||||
#define OT_LAYOUT_GSUB_LIGATURESET_HH
|
||||
|
||||
#include "Common.hh"
|
||||
#include "Ligature.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
template <typename Types>
|
||||
struct LigatureSet
|
||||
{
|
||||
protected:
|
||||
Array16OfOffset16To<Ligature<Types>>
|
||||
ligature; /* Array LigatureSet tables
|
||||
* ordered by preference */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (2, ligature);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (ligature.sanitize (c, this));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
return
|
||||
+ hb_iter (ligature)
|
||||
| hb_map (hb_add (this))
|
||||
| hb_map ([glyphs] (const Ligature<Types> &_) { return _.intersects (glyphs); })
|
||||
| hb_any
|
||||
;
|
||||
}
|
||||
|
||||
bool intersects_lig_glyph (const hb_set_t *glyphs) const
|
||||
{
|
||||
return
|
||||
+ hb_iter (ligature)
|
||||
| hb_map (hb_add (this))
|
||||
| hb_map ([glyphs] (const Ligature<Types> &_) {
|
||||
return _.intersects_lig_glyph (glyphs) && _.intersects (glyphs);
|
||||
})
|
||||
| hb_any
|
||||
;
|
||||
}
|
||||
|
||||
void closure (hb_closure_context_t *c) const
|
||||
{
|
||||
+ hb_iter (ligature)
|
||||
| hb_map (hb_add (this))
|
||||
| hb_apply ([c] (const Ligature<Types> &_) { _.closure (c); })
|
||||
;
|
||||
}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
+ hb_iter (ligature)
|
||||
| hb_map (hb_add (this))
|
||||
| hb_apply ([c] (const Ligature<Types> &_) { _.collect_glyphs (c); })
|
||||
;
|
||||
}
|
||||
|
||||
bool would_apply (hb_would_apply_context_t *c) const
|
||||
{
|
||||
return
|
||||
+ hb_iter (ligature)
|
||||
| hb_map (hb_add (this))
|
||||
| hb_map ([c] (const Ligature<Types> &_) { return _.would_apply (c); })
|
||||
| hb_any
|
||||
;
|
||||
}
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
|
||||
unsigned int num_ligs = ligature.len;
|
||||
|
||||
#ifndef HB_NO_OT_RULESETS_FAST_PATH
|
||||
if (HB_OPTIMIZE_SIZE_VAL || num_ligs <= 4)
|
||||
#endif
|
||||
{
|
||||
slow:
|
||||
for (unsigned int i = 0; i < num_ligs; i++)
|
||||
{
|
||||
const auto &lig = this+ligature.arrayZ[i];
|
||||
if (lig.apply (c)) return_trace (true);
|
||||
}
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
/* This version is optimized for speed by matching the first component
|
||||
* of the ligature here, instead of calling into the ligation code.
|
||||
*
|
||||
* This is replicated in ChainRuleSet and RuleSet. */
|
||||
|
||||
hb_ot_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_input;
|
||||
skippy_iter.reset (c->buffer->idx);
|
||||
skippy_iter.set_match_func (match_always, nullptr);
|
||||
skippy_iter.set_glyph_data ((HBUINT16 *) nullptr);
|
||||
unsigned unsafe_to;
|
||||
hb_codepoint_t first = (unsigned) -1;
|
||||
bool matched = skippy_iter.next (&unsafe_to);
|
||||
if (likely (matched))
|
||||
{
|
||||
first = c->buffer->info[skippy_iter.idx].codepoint;
|
||||
unsafe_to = skippy_iter.idx + 1;
|
||||
|
||||
if (skippy_iter.may_skip (c->buffer->info[skippy_iter.idx]))
|
||||
{
|
||||
/* Can't use the fast path if eg. the next char is a default-ignorable
|
||||
* or other skippable. */
|
||||
goto slow;
|
||||
}
|
||||
}
|
||||
else
|
||||
goto slow;
|
||||
|
||||
bool unsafe_to_concat = false;
|
||||
|
||||
for (unsigned int i = 0; i < num_ligs; i++)
|
||||
{
|
||||
const auto &lig = this+ligature.arrayZ[i];
|
||||
if (unlikely (lig.component.lenP1 <= 1) ||
|
||||
lig.component.arrayZ[0] == first)
|
||||
{
|
||||
if (lig.apply (c))
|
||||
{
|
||||
if (unsafe_to_concat)
|
||||
c->buffer->unsafe_to_concat (c->buffer->idx, unsafe_to);
|
||||
return_trace (true);
|
||||
}
|
||||
}
|
||||
else if (likely (lig.component.lenP1 > 1))
|
||||
unsafe_to_concat = true;
|
||||
}
|
||||
if (likely (unsafe_to_concat))
|
||||
c->buffer->unsafe_to_concat (c->buffer->idx, unsafe_to);
|
||||
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
hb_array_t<const HBGlyphID16> ligatures,
|
||||
hb_array_t<const unsigned int> component_count_list,
|
||||
hb_array_t<const HBGlyphID16> &component_list /* Starting from second for each ligature */)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!c->extend_min (this))) return_trace (false);
|
||||
if (unlikely (!ligature.serialize (c, ligatures.length))) return_trace (false);
|
||||
for (unsigned int i = 0; i < ligatures.length; i++)
|
||||
{
|
||||
unsigned int component_count = (unsigned) hb_max ((int) component_count_list[i] - 1, 0);
|
||||
if (unlikely (!ligature[i].serialize_serialize (c,
|
||||
ligatures[i],
|
||||
component_list.sub_array (0, component_count))))
|
||||
return_trace (false);
|
||||
component_list += component_count;
|
||||
}
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c, unsigned coverage_idx) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
|
||||
+ hb_iter (ligature)
|
||||
| hb_filter (subset_offset_array (c, out->ligature, this, coverage_idx))
|
||||
| hb_drain
|
||||
;
|
||||
|
||||
if (bool (out->ligature))
|
||||
// Ensure Coverage table is always packed after this.
|
||||
c->serializer->add_virtual_link (coverage_idx);
|
||||
|
||||
return_trace (bool (out->ligature));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_LIGATURESET_HH */
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
#ifndef OT_LAYOUT_GSUB_LIGATURESUBST_HH
|
||||
#define OT_LAYOUT_GSUB_LIGATURESUBST_HH
|
||||
|
||||
#include "Common.hh"
|
||||
#include "LigatureSubstFormat1.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
struct LigatureSubst
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
LigatureSubstFormat1_2<SmallTypes> format1;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
LigatureSubstFormat1_2<MediumTypes> format2;
|
||||
#endif
|
||||
} u;
|
||||
|
||||
public:
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{
|
||||
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
|
||||
TRACE_DISPATCH (this, u.format);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
|
||||
#endif
|
||||
default:return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO This function is only used by small GIDs, and not updated to 24bit GIDs. Should
|
||||
* be done by using iterators. While at it perhaps using iterator of arrays of hb_codepoint_t
|
||||
* instead. */
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
hb_sorted_array_t<const HBGlyphID16> first_glyphs,
|
||||
hb_array_t<const unsigned int> ligature_per_first_glyph_count_list,
|
||||
hb_array_t<const HBGlyphID16> ligatures_list,
|
||||
hb_array_t<const unsigned int> component_count_list,
|
||||
hb_array_t<const HBGlyphID16> component_list /* Starting from second for each ligature */)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!c->extend_min (u.format))) return_trace (false);
|
||||
unsigned int format = 1;
|
||||
u.format = format;
|
||||
switch (u.format) {
|
||||
case 1: return_trace (u.format1.serialize (c,
|
||||
first_glyphs,
|
||||
ligature_per_first_glyph_count_list,
|
||||
ligatures_list,
|
||||
component_count_list,
|
||||
component_list));
|
||||
default:return_trace (false);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO subset() should choose format. */
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_LIGATURESUBST_HH */
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
#ifndef OT_LAYOUT_GSUB_LIGATURESUBSTFORMAT1_HH
|
||||
#define OT_LAYOUT_GSUB_LIGATURESUBSTFORMAT1_HH
|
||||
|
||||
#include "Common.hh"
|
||||
#include "LigatureSet.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
template <typename Types>
|
||||
struct LigatureSubstFormat1_2
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
coverage; /* Offset to Coverage table--from
|
||||
* beginning of Substitution table */
|
||||
Array16Of<typename Types::template OffsetTo<LigatureSet<Types>>>
|
||||
ligatureSet; /* Array LigatureSet tables
|
||||
* ordered by Coverage Index */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (4 + Types::size, ligatureSet);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (coverage.sanitize (c, this) && ligatureSet.sanitize (c, this));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
return
|
||||
+ hb_zip (this+coverage, ligatureSet)
|
||||
| hb_filter (*glyphs, hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_map ([this, glyphs] (const typename Types::template OffsetTo<LigatureSet<Types>> &_)
|
||||
{ return (this+_).intersects (glyphs); })
|
||||
| hb_any
|
||||
;
|
||||
}
|
||||
|
||||
bool may_have_non_1to1 () const
|
||||
{ return true; }
|
||||
|
||||
void closure (hb_closure_context_t *c) const
|
||||
{
|
||||
+ hb_zip (this+coverage, ligatureSet)
|
||||
| hb_filter (c->parent_active_glyphs (), hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_map (hb_add (this))
|
||||
| hb_apply ([c] (const LigatureSet<Types> &_) { _.closure (c); })
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
|
||||
|
||||
+ hb_zip (this+coverage, ligatureSet)
|
||||
| hb_map (hb_second)
|
||||
| hb_map (hb_add (this))
|
||||
| hb_apply ([c] (const LigatureSet<Types> &_) { _.collect_glyphs (c); })
|
||||
;
|
||||
}
|
||||
|
||||
const Coverage &get_coverage () const { return this+coverage; }
|
||||
|
||||
bool would_apply (hb_would_apply_context_t *c) const
|
||||
{
|
||||
unsigned int index = (this+coverage).get_coverage (c->glyphs[0]);
|
||||
if (likely (index == NOT_COVERED)) return false;
|
||||
|
||||
const auto &lig_set = this+ligatureSet[index];
|
||||
return lig_set.would_apply (c);
|
||||
}
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
|
||||
unsigned int index = (this+coverage).get_coverage (c->buffer->cur ().codepoint);
|
||||
if (likely (index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
const auto &lig_set = this+ligatureSet[index];
|
||||
return_trace (lig_set.apply (c));
|
||||
}
|
||||
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
hb_sorted_array_t<const HBGlyphID16> first_glyphs,
|
||||
hb_array_t<const unsigned int> ligature_per_first_glyph_count_list,
|
||||
hb_array_t<const HBGlyphID16> ligatures_list,
|
||||
hb_array_t<const unsigned int> component_count_list,
|
||||
hb_array_t<const HBGlyphID16> component_list /* Starting from second for each ligature */)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!c->extend_min (this))) return_trace (false);
|
||||
if (unlikely (!ligatureSet.serialize (c, first_glyphs.length))) return_trace (false);
|
||||
for (unsigned int i = 0; i < first_glyphs.length; i++)
|
||||
{
|
||||
unsigned int ligature_count = ligature_per_first_glyph_count_list[i];
|
||||
if (unlikely (!ligatureSet[i]
|
||||
.serialize_serialize (c,
|
||||
ligatures_list.sub_array (0, ligature_count),
|
||||
component_count_list.sub_array (0, ligature_count),
|
||||
component_list))) return_trace (false);
|
||||
ligatures_list += ligature_count;
|
||||
component_count_list += ligature_count;
|
||||
}
|
||||
return_trace (coverage.serialize_serialize (c, first_glyphs));
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
out->format = format;
|
||||
|
||||
// Due to a bug in some older versions of windows 7 the Coverage table must be
|
||||
// packed after the LigatureSet and Ligature tables, so serialize Coverage first
|
||||
// which places it last in the packed order.
|
||||
hb_set_t new_coverage;
|
||||
+ hb_zip (this+coverage, hb_iter (ligatureSet) | hb_map (hb_add (this)))
|
||||
| hb_filter (glyphset, hb_first)
|
||||
| hb_filter ([&] (const LigatureSet<Types>& _) {
|
||||
return _.intersects_lig_glyph (&glyphset);
|
||||
}, hb_second)
|
||||
| hb_map (hb_first)
|
||||
| hb_sink (new_coverage);
|
||||
|
||||
if (!c->serializer->push<Coverage> ()
|
||||
->serialize (c->serializer,
|
||||
+ new_coverage.iter () | hb_map_retains_sorting (glyph_map)))
|
||||
{
|
||||
c->serializer->pop_discard ();
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
unsigned coverage_idx = c->serializer->pop_pack ();
|
||||
c->serializer->add_link (out->coverage, coverage_idx);
|
||||
|
||||
+ hb_zip (this+coverage, ligatureSet)
|
||||
| hb_filter (new_coverage, hb_first)
|
||||
| hb_map (hb_second)
|
||||
// to ensure that the repacker always orders the coverage table after the LigatureSet
|
||||
// and LigatureSubtable's they will be linked to the Coverage table via a virtual link
|
||||
// the coverage table object idx is passed down to facilitate this.
|
||||
| hb_apply (subset_offset_array (c, out->ligatureSet, this, coverage_idx))
|
||||
;
|
||||
|
||||
return_trace (bool (new_coverage));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_LIGATURESUBSTFORMAT1_HH */
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
#ifndef OT_LAYOUT_GSUB_MULTIPLESUBST_HH
|
||||
#define OT_LAYOUT_GSUB_MULTIPLESUBST_HH
|
||||
|
||||
#include "Common.hh"
|
||||
#include "MultipleSubstFormat1.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
struct MultipleSubst
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
MultipleSubstFormat1_2<SmallTypes> format1;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
MultipleSubstFormat1_2<MediumTypes> format2;
|
||||
#endif
|
||||
} u;
|
||||
|
||||
public:
|
||||
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{
|
||||
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
|
||||
TRACE_DISPATCH (this, u.format);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
|
||||
#endif
|
||||
default:return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Iterator,
|
||||
hb_requires (hb_is_sorted_iterator (Iterator))>
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
Iterator it)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!c->extend_min (u.format))) return_trace (false);
|
||||
unsigned int format = 1;
|
||||
u.format = format;
|
||||
switch (u.format) {
|
||||
case 1: return_trace (u.format1.serialize (c, it));
|
||||
default:return_trace (false);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO subset() should choose format. */
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_MULTIPLESUBST_HH */
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
#ifndef OT_LAYOUT_GSUB_MULTIPLESUBSTFORMAT1_HH
|
||||
#define OT_LAYOUT_GSUB_MULTIPLESUBSTFORMAT1_HH
|
||||
|
||||
#include "Common.hh"
|
||||
#include "Sequence.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
template <typename Types>
|
||||
struct MultipleSubstFormat1_2
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
coverage; /* Offset to Coverage table--from
|
||||
* beginning of Substitution table */
|
||||
Array16Of<typename Types::template OffsetTo<Sequence<Types>>>
|
||||
sequence; /* Array of Sequence tables
|
||||
* ordered by Coverage Index */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (4 + Types::size, sequence);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (coverage.sanitize (c, this) && sequence.sanitize (c, this));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{ return (this+coverage).intersects (glyphs); }
|
||||
|
||||
bool may_have_non_1to1 () const
|
||||
{ return true; }
|
||||
|
||||
void closure (hb_closure_context_t *c) const
|
||||
{
|
||||
+ hb_zip (this+coverage, sequence)
|
||||
| hb_filter (c->parent_active_glyphs (), hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_map (hb_add (this))
|
||||
| hb_apply ([c] (const Sequence<Types> &_) { _.closure (c); })
|
||||
;
|
||||
}
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
|
||||
+ hb_zip (this+coverage, sequence)
|
||||
| hb_map (hb_second)
|
||||
| hb_map (hb_add (this))
|
||||
| hb_apply ([c] (const Sequence<Types> &_) { _.collect_glyphs (c); })
|
||||
;
|
||||
}
|
||||
|
||||
const Coverage &get_coverage () const { return this+coverage; }
|
||||
|
||||
bool would_apply (hb_would_apply_context_t *c) const
|
||||
{ return c->len == 1 && (this+coverage).get_coverage (c->glyphs[0]) != NOT_COVERED; }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
|
||||
unsigned int index = (this+coverage).get_coverage (c->buffer->cur().codepoint);
|
||||
if (likely (index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
return_trace ((this+sequence[index]).apply (c));
|
||||
}
|
||||
|
||||
template<typename Iterator,
|
||||
hb_requires (hb_is_sorted_iterator (Iterator))>
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
Iterator it)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
auto sequences =
|
||||
+ it
|
||||
| hb_map (hb_second)
|
||||
;
|
||||
auto glyphs =
|
||||
+ it
|
||||
| hb_map_retains_sorting (hb_first)
|
||||
;
|
||||
if (unlikely (!c->extend_min (this))) return_trace (false);
|
||||
|
||||
if (unlikely (!sequence.serialize (c, sequences.length))) return_trace (false);
|
||||
|
||||
for (auto& pair : hb_zip (sequences, sequence))
|
||||
{
|
||||
if (unlikely (!pair.second
|
||||
.serialize_serialize (c, pair.first)))
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
return_trace (coverage.serialize_serialize (c, glyphs));
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
if (unlikely (!c->serializer->extend_min (out))) return_trace (false);
|
||||
out->format = format;
|
||||
|
||||
hb_sorted_vector_t<hb_codepoint_t> new_coverage;
|
||||
+ hb_zip (this+coverage, sequence)
|
||||
| hb_filter (glyphset, hb_first)
|
||||
| hb_filter (subset_offset_array (c, out->sequence, this), hb_second)
|
||||
| hb_map (hb_first)
|
||||
| hb_map (glyph_map)
|
||||
| hb_sink (new_coverage)
|
||||
;
|
||||
out->coverage.serialize_serialize (c->serializer, new_coverage.iter ());
|
||||
return_trace (bool (new_coverage));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_MULTIPLESUBSTFORMAT1_HH */
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#ifndef OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBST_HH
|
||||
#define OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBST_HH
|
||||
|
||||
#include "Common.hh"
|
||||
#include "ReverseChainSingleSubstFormat1.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
struct ReverseChainSingleSubst
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
ReverseChainSingleSubstFormat1 format1;
|
||||
} u;
|
||||
|
||||
public:
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{
|
||||
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
|
||||
TRACE_DISPATCH (this, u.format);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
|
||||
default:return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* HB_OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBST_HH */
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
#ifndef OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBSTFORMAT1_HH
|
||||
#define OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBSTFORMAT1_HH
|
||||
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
struct ReverseChainSingleSubstFormat1
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
Offset16To<Coverage>
|
||||
coverage; /* Offset to Coverage table--from
|
||||
* beginning of table */
|
||||
Array16OfOffset16To<Coverage>
|
||||
backtrack; /* Array of coverage tables
|
||||
* in backtracking sequence, in glyph
|
||||
* sequence order */
|
||||
Array16OfOffset16To<Coverage>
|
||||
lookaheadX; /* Array of coverage tables
|
||||
* in lookahead sequence, in glyph
|
||||
* sequence order */
|
||||
Array16Of<HBGlyphID16>
|
||||
substituteX; /* Array of substitute
|
||||
* GlyphIDs--ordered by Coverage Index */
|
||||
public:
|
||||
DEFINE_SIZE_MIN (10);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
if (!(coverage.sanitize (c, this) && backtrack.sanitize (c, this)))
|
||||
return_trace (false);
|
||||
hb_barrier ();
|
||||
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
|
||||
if (!lookahead.sanitize (c, this))
|
||||
return_trace (false);
|
||||
hb_barrier ();
|
||||
const auto &substitute = StructAfter<decltype (substituteX)> (lookahead);
|
||||
return_trace (substitute.sanitize (c));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
if (!(this+coverage).intersects (glyphs))
|
||||
return false;
|
||||
|
||||
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
|
||||
|
||||
unsigned int count;
|
||||
|
||||
count = backtrack.len;
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
if (!(this+backtrack[i]).intersects (glyphs))
|
||||
return false;
|
||||
|
||||
count = lookahead.len;
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
if (!(this+lookahead[i]).intersects (glyphs))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool may_have_non_1to1 () const
|
||||
{ return false; }
|
||||
|
||||
void closure (hb_closure_context_t *c) const
|
||||
{
|
||||
if (!intersects (c->glyphs)) return;
|
||||
|
||||
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
|
||||
const auto &substitute = StructAfter<decltype (substituteX)> (lookahead);
|
||||
|
||||
+ hb_zip (this+coverage, substitute)
|
||||
| hb_filter (c->parent_active_glyphs (), hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_sink (c->output)
|
||||
;
|
||||
}
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
|
||||
|
||||
unsigned int count;
|
||||
|
||||
count = backtrack.len;
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
if (unlikely (!(this+backtrack[i]).collect_coverage (c->before))) return;
|
||||
|
||||
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
|
||||
count = lookahead.len;
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
if (unlikely (!(this+lookahead[i]).collect_coverage (c->after))) return;
|
||||
|
||||
const auto &substitute = StructAfter<decltype (substituteX)> (lookahead);
|
||||
count = substitute.len;
|
||||
c->output->add_array (substitute.arrayZ, substitute.len);
|
||||
}
|
||||
|
||||
const Coverage &get_coverage () const { return this+coverage; }
|
||||
|
||||
bool would_apply (hb_would_apply_context_t *c) const
|
||||
{ return c->len == 1 && (this+coverage).get_coverage (c->glyphs[0]) != NOT_COVERED; }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
unsigned int index = (this+coverage).get_coverage (c->buffer->cur ().codepoint);
|
||||
if (likely (index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
if (unlikely (c->nesting_level_left != HB_MAX_NESTING_LEVEL))
|
||||
return_trace (false); /* No chaining to this type */
|
||||
|
||||
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
|
||||
const auto &substitute = StructAfter<decltype (substituteX)> (lookahead);
|
||||
|
||||
if (unlikely (index >= substitute.len)) return_trace (false);
|
||||
|
||||
unsigned int start_index = 0, end_index = 0;
|
||||
if (match_backtrack (c,
|
||||
backtrack.len, (HBUINT16 *) backtrack.arrayZ,
|
||||
match_coverage, this,
|
||||
&start_index) &&
|
||||
match_lookahead (c,
|
||||
lookahead.len, (HBUINT16 *) lookahead.arrayZ,
|
||||
match_coverage, this,
|
||||
c->buffer->idx + 1, &end_index))
|
||||
{
|
||||
c->buffer->unsafe_to_break_from_outbuffer (start_index, end_index);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"replacing glyph at %u (reverse chaining substitution)",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
c->replace_glyph_inplace (substitute[index]);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"replaced glyph at %u (reverse chaining substitution)",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
/* Note: We DON'T decrease buffer->idx. The main loop does it
|
||||
* for us. This is useful for preventing surprises if someone
|
||||
* calls us through a Context lookup. */
|
||||
return_trace (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
c->buffer->unsafe_to_concat_from_outbuffer (start_index, end_index);
|
||||
return_trace (false);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Iterator,
|
||||
hb_requires (hb_is_iterator (Iterator))>
|
||||
bool serialize_coverage_offset_array (hb_subset_context_t *c, Iterator it) const
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
auto *out = c->serializer->start_embed<Array16OfOffset16To<Coverage>> ();
|
||||
|
||||
if (unlikely (!c->serializer->allocate_size<HBUINT16> (HBUINT16::static_size)))
|
||||
return_trace (false);
|
||||
|
||||
for (auto& offset : it) {
|
||||
auto *o = out->serialize_append (c->serializer);
|
||||
if (unlikely (!o) || !o->serialize_subset (c, offset, this))
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
template<typename Iterator, typename BacktrackIterator, typename LookaheadIterator,
|
||||
hb_requires (hb_is_sorted_source_of (Iterator, hb_codepoint_pair_t)),
|
||||
hb_requires (hb_is_iterator (BacktrackIterator)),
|
||||
hb_requires (hb_is_iterator (LookaheadIterator))>
|
||||
bool serialize (hb_subset_context_t *c,
|
||||
Iterator coverage_subst_iter,
|
||||
BacktrackIterator backtrack_iter,
|
||||
LookaheadIterator lookahead_iter) const
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
|
||||
auto *out = c->serializer->start_embed (this);
|
||||
if (unlikely (!c->serializer->embed (this->format))) return_trace (false);
|
||||
if (unlikely (!c->serializer->embed (this->coverage))) return_trace (false);
|
||||
|
||||
if (!serialize_coverage_offset_array (c, backtrack_iter)) return_trace (false);
|
||||
if (!serialize_coverage_offset_array (c, lookahead_iter)) return_trace (false);
|
||||
|
||||
auto *substitute_out = c->serializer->start_embed<Array16Of<HBGlyphID16>> ();
|
||||
auto substitutes =
|
||||
+ coverage_subst_iter
|
||||
| hb_map (hb_second)
|
||||
;
|
||||
|
||||
auto glyphs =
|
||||
+ coverage_subst_iter
|
||||
| hb_map_retains_sorting (hb_first)
|
||||
;
|
||||
if (unlikely (! c->serializer->check_success (substitute_out->serialize (c->serializer, substitutes))))
|
||||
return_trace (false);
|
||||
|
||||
if (unlikely (!out->coverage.serialize_serialize (c->serializer, glyphs)))
|
||||
return_trace (false);
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
const auto &lookahead = StructAfter<decltype (lookaheadX)> (backtrack);
|
||||
const auto &substitute = StructAfter<decltype (substituteX)> (lookahead);
|
||||
|
||||
auto it =
|
||||
+ hb_zip (this+coverage, substitute)
|
||||
| hb_filter (glyphset, hb_first)
|
||||
| hb_filter (glyphset, hb_second)
|
||||
| hb_map_retains_sorting ([&] (hb_pair_t<hb_codepoint_t, const HBGlyphID16 &> p) -> hb_codepoint_pair_t
|
||||
{ return hb_pair (glyph_map[p.first], glyph_map[p.second]); })
|
||||
;
|
||||
|
||||
return_trace (bool (it) && serialize (c, it, backtrack.iter (), lookahead.iter ()));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* HB_OT_LAYOUT_GSUB_REVERSECHAINSINGLESUBSTFORMAT1_HH */
|
||||
@@ -0,0 +1,165 @@
|
||||
#ifndef OT_LAYOUT_GSUB_SEQUENCE_HH
|
||||
#define OT_LAYOUT_GSUB_SEQUENCE_HH
|
||||
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
template <typename Types>
|
||||
struct Sequence
|
||||
{
|
||||
protected:
|
||||
Array16Of<typename Types::HBGlyphID>
|
||||
substitute; /* String of GlyphIDs to substitute */
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (2, substitute);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (substitute.sanitize (c));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{ return hb_all (substitute, glyphs); }
|
||||
|
||||
void closure (hb_closure_context_t *c) const
|
||||
{ c->output->add_array (substitute.arrayZ, substitute.len); }
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{ c->output->add_array (substitute.arrayZ, substitute.len); }
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
unsigned int count = substitute.len;
|
||||
|
||||
/* Special-case to make it in-place and not consider this
|
||||
* as a "multiplied" substitution. */
|
||||
if (unlikely (count == 1))
|
||||
{
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->sync_so_far ();
|
||||
c->buffer->message (c->font,
|
||||
"replacing glyph at %u (multiple substitution)",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
c->replace_glyph (substitute.arrayZ[0]);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"replaced glyph at %u (multiple substitution)",
|
||||
c->buffer->idx - 1u);
|
||||
}
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
/* Spec disallows this, but Uniscribe allows it.
|
||||
* https://github.com/harfbuzz/harfbuzz/issues/253 */
|
||||
else if (unlikely (count == 0))
|
||||
{
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->sync_so_far ();
|
||||
c->buffer->message (c->font,
|
||||
"deleting glyph at %u (multiple substitution)",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
c->buffer->delete_glyph ();
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->sync_so_far ();
|
||||
c->buffer->message (c->font,
|
||||
"deleted glyph at %u (multiple substitution)",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->sync_so_far ();
|
||||
c->buffer->message (c->font,
|
||||
"multiplying glyph at %u",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
unsigned int klass = _hb_glyph_info_is_ligature (&c->buffer->cur()) ?
|
||||
HB_OT_LAYOUT_GLYPH_PROPS_BASE_GLYPH : 0;
|
||||
unsigned lig_id = _hb_glyph_info_get_lig_id (&c->buffer->cur());
|
||||
|
||||
for (unsigned int i = 0; i < count; i++)
|
||||
{
|
||||
/* If is attached to a ligature, don't disturb that.
|
||||
* https://github.com/harfbuzz/harfbuzz/issues/3069 */
|
||||
if (!lig_id)
|
||||
_hb_glyph_info_set_lig_props_for_component (&c->buffer->cur(), i);
|
||||
c->output_glyph_for_component (substitute.arrayZ[i], klass);
|
||||
}
|
||||
c->buffer->skip_glyph ();
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->sync_so_far ();
|
||||
|
||||
char buf[HB_MAX_CONTEXT_LENGTH * 16] = {0};
|
||||
char *p = buf;
|
||||
|
||||
for (unsigned i = c->buffer->idx - count; i < c->buffer->idx; i++)
|
||||
{
|
||||
if (buf < p)
|
||||
*p++ = ',';
|
||||
snprintf (p, sizeof(buf) - (p - buf), "%u", i);
|
||||
p += strlen(p);
|
||||
}
|
||||
|
||||
c->buffer->message (c->font,
|
||||
"multiplied glyphs at %s",
|
||||
buf);
|
||||
}
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
template <typename Iterator,
|
||||
hb_requires (hb_is_source_of (Iterator, hb_codepoint_t))>
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
Iterator subst)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
return_trace (substitute.serialize (c, subst));
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
if (!intersects (&glyphset)) return_trace (false);
|
||||
|
||||
auto it =
|
||||
+ hb_iter (substitute)
|
||||
| hb_map (glyph_map)
|
||||
;
|
||||
|
||||
auto *out = c->serializer->start_embed (*this);
|
||||
return_trace (out->serialize (c->serializer, it));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_SEQUENCE_HH */
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
#ifndef OT_LAYOUT_GSUB_SINGLESUBST_HH
|
||||
#define OT_LAYOUT_GSUB_SINGLESUBST_HH
|
||||
|
||||
#include "Common.hh"
|
||||
#include "SingleSubstFormat1.hh"
|
||||
#include "SingleSubstFormat2.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
struct SingleSubst
|
||||
{
|
||||
protected:
|
||||
union {
|
||||
HBUINT16 format; /* Format identifier */
|
||||
SingleSubstFormat1_3<SmallTypes> format1;
|
||||
SingleSubstFormat2_4<SmallTypes> format2;
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
SingleSubstFormat1_3<MediumTypes> format3;
|
||||
SingleSubstFormat2_4<MediumTypes> format4;
|
||||
#endif
|
||||
} u;
|
||||
|
||||
public:
|
||||
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{
|
||||
if (unlikely (!c->may_dispatch (this, &u.format))) return c->no_dispatch_return_value ();
|
||||
TRACE_DISPATCH (this, u.format);
|
||||
switch (u.format) {
|
||||
case 1: return_trace (c->dispatch (u.format1, std::forward<Ts> (ds)...));
|
||||
case 2: return_trace (c->dispatch (u.format2, std::forward<Ts> (ds)...));
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return_trace (c->dispatch (u.format3, std::forward<Ts> (ds)...));
|
||||
case 4: return_trace (c->dispatch (u.format4, std::forward<Ts> (ds)...));
|
||||
#endif
|
||||
default:return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Iterator,
|
||||
hb_requires (hb_is_sorted_source_of (Iterator,
|
||||
const hb_codepoint_pair_t))>
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
Iterator glyphs)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!c->extend_min (u.format))) return_trace (false);
|
||||
unsigned format = 2;
|
||||
unsigned delta = 0;
|
||||
if (glyphs)
|
||||
{
|
||||
format = 1;
|
||||
hb_codepoint_t mask = 0xFFFFu;
|
||||
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
if (+ glyphs
|
||||
| hb_map_retains_sorting (hb_second)
|
||||
| hb_filter ([] (hb_codepoint_t gid) { return gid > 0xFFFFu; }))
|
||||
{
|
||||
format += 2;
|
||||
mask = 0xFFFFFFu;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto get_delta = [=] (hb_codepoint_pair_t _)
|
||||
{ return (unsigned) (_.second - _.first) & mask; };
|
||||
delta = get_delta (*glyphs);
|
||||
if (!hb_all (++(+glyphs), delta, get_delta)) format += 1;
|
||||
}
|
||||
|
||||
u.format = format;
|
||||
switch (u.format) {
|
||||
case 1: return_trace (u.format1.serialize (c,
|
||||
+ glyphs
|
||||
| hb_map_retains_sorting (hb_first),
|
||||
delta));
|
||||
case 2: return_trace (u.format2.serialize (c, glyphs));
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
case 3: return_trace (u.format3.serialize (c,
|
||||
+ glyphs
|
||||
| hb_map_retains_sorting (hb_first),
|
||||
delta));
|
||||
case 4: return_trace (u.format4.serialize (c, glyphs));
|
||||
#endif
|
||||
default:return_trace (false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Iterator>
|
||||
static void
|
||||
SingleSubst_serialize (hb_serialize_context_t *c,
|
||||
Iterator it)
|
||||
{ c->start_embed<SingleSubst> ()->serialize (c, it); }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_SINGLESUBST_HH */
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
#ifndef OT_LAYOUT_GSUB_SINGLESUBSTFORMAT1_HH
|
||||
#define OT_LAYOUT_GSUB_SINGLESUBSTFORMAT1_HH
|
||||
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
template <typename Types>
|
||||
struct SingleSubstFormat1_3
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 1 */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
coverage; /* Offset to Coverage table--from
|
||||
* beginning of Substitution table */
|
||||
typename Types::HBUINT
|
||||
deltaGlyphID; /* Add to original GlyphID to get
|
||||
* substitute GlyphID, modulo 0x10000 */
|
||||
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (2 + 2 * Types::size);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (c->check_struct (this) &&
|
||||
coverage.sanitize (c, this) &&
|
||||
/* The coverage table may use a range to represent a set
|
||||
* of glyphs, which means a small number of bytes can
|
||||
* generate a large glyph set. Manually modify the
|
||||
* sanitizer max ops to take this into account.
|
||||
*
|
||||
* Note: This check *must* be right after coverage sanitize. */
|
||||
c->check_ops ((this + coverage).get_population () >> 1));
|
||||
}
|
||||
|
||||
hb_codepoint_t get_mask () const
|
||||
{ return (1 << (8 * Types::size)) - 1; }
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{ return (this+coverage).intersects (glyphs); }
|
||||
|
||||
bool may_have_non_1to1 () const
|
||||
{ return false; }
|
||||
|
||||
void closure (hb_closure_context_t *c) const
|
||||
{
|
||||
hb_codepoint_t d = deltaGlyphID;
|
||||
hb_codepoint_t mask = get_mask ();
|
||||
|
||||
/* Help fuzzer avoid this function as much. */
|
||||
unsigned pop = (this+coverage).get_population ();
|
||||
if (pop >= mask)
|
||||
return;
|
||||
|
||||
hb_set_t intersection;
|
||||
(this+coverage).intersect_set (c->parent_active_glyphs (), intersection);
|
||||
|
||||
/* In degenerate fuzzer-found fonts, but not real fonts,
|
||||
* this table can keep adding new glyphs in each round of closure.
|
||||
* Refuse to close-over, if it maps glyph range to overlapping range. */
|
||||
hb_codepoint_t min_before = intersection.get_min ();
|
||||
hb_codepoint_t max_before = intersection.get_max ();
|
||||
hb_codepoint_t min_after = (min_before + d) & mask;
|
||||
hb_codepoint_t max_after = (max_before + d) & mask;
|
||||
if (intersection.get_population () == max_before - min_before + 1 &&
|
||||
((min_before <= min_after && min_after <= max_before) ||
|
||||
(min_before <= max_after && max_after <= max_before)))
|
||||
return;
|
||||
|
||||
+ hb_iter (intersection)
|
||||
| hb_map ([d, mask] (hb_codepoint_t g) { return (g + d) & mask; })
|
||||
| hb_sink (c->output)
|
||||
;
|
||||
}
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
|
||||
hb_codepoint_t d = deltaGlyphID;
|
||||
hb_codepoint_t mask = get_mask ();
|
||||
|
||||
+ hb_iter (this+coverage)
|
||||
| hb_map ([d, mask] (hb_codepoint_t g) { return (g + d) & mask; })
|
||||
| hb_sink (c->output)
|
||||
;
|
||||
}
|
||||
|
||||
const Coverage &get_coverage () const { return this+coverage; }
|
||||
|
||||
bool would_apply (hb_would_apply_context_t *c) const
|
||||
{ return c->len == 1 && (this+coverage).get_coverage (c->glyphs[0]) != NOT_COVERED; }
|
||||
|
||||
unsigned
|
||||
get_glyph_alternates (hb_codepoint_t glyph_id,
|
||||
unsigned start_offset,
|
||||
unsigned *alternate_count /* IN/OUT. May be NULL. */,
|
||||
hb_codepoint_t *alternate_glyphs /* OUT. May be NULL. */) const
|
||||
{
|
||||
unsigned int index = (this+coverage).get_coverage (glyph_id);
|
||||
if (likely (index == NOT_COVERED))
|
||||
{
|
||||
if (alternate_count)
|
||||
*alternate_count = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (alternate_count && *alternate_count)
|
||||
{
|
||||
hb_codepoint_t d = deltaGlyphID;
|
||||
hb_codepoint_t mask = get_mask ();
|
||||
|
||||
glyph_id = (glyph_id + d) & mask;
|
||||
|
||||
*alternate_glyphs = glyph_id;
|
||||
*alternate_count = 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
hb_codepoint_t glyph_id = c->buffer->cur().codepoint;
|
||||
unsigned int index = (this+coverage).get_coverage (glyph_id);
|
||||
if (likely (index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
hb_codepoint_t d = deltaGlyphID;
|
||||
hb_codepoint_t mask = get_mask ();
|
||||
|
||||
glyph_id = (glyph_id + d) & mask;
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->sync_so_far ();
|
||||
c->buffer->message (c->font,
|
||||
"replacing glyph at %u (single substitution)",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
c->replace_glyph (glyph_id);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"replaced glyph at %u (single substitution)",
|
||||
c->buffer->idx - 1u);
|
||||
}
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
template<typename Iterator,
|
||||
hb_requires (hb_is_sorted_source_of (Iterator, hb_codepoint_t))>
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
Iterator glyphs,
|
||||
unsigned delta)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!c->extend_min (this))) return_trace (false);
|
||||
if (unlikely (!coverage.serialize_serialize (c, glyphs))) return_trace (false);
|
||||
c->check_assign (deltaGlyphID, delta, HB_SERIALIZE_ERROR_INT_OVERFLOW);
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
hb_codepoint_t d = deltaGlyphID;
|
||||
hb_codepoint_t mask = get_mask ();
|
||||
|
||||
hb_set_t intersection;
|
||||
(this+coverage).intersect_set (glyphset, intersection);
|
||||
|
||||
auto it =
|
||||
+ hb_iter (intersection)
|
||||
| hb_map_retains_sorting ([d, mask] (hb_codepoint_t g) {
|
||||
return hb_codepoint_pair_t (g,
|
||||
(g + d) & mask); })
|
||||
| hb_filter (glyphset, hb_second)
|
||||
| hb_map_retains_sorting ([&] (hb_codepoint_pair_t p) -> hb_codepoint_pair_t
|
||||
{ return hb_pair (glyph_map[p.first], glyph_map[p.second]); })
|
||||
;
|
||||
|
||||
bool ret = bool (it);
|
||||
SingleSubst_serialize (c->serializer, it);
|
||||
return_trace (ret);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_SINGLESUBSTFORMAT1_HH */
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
#ifndef OT_LAYOUT_GSUB_SINGLESUBSTFORMAT2_HH
|
||||
#define OT_LAYOUT_GSUB_SINGLESUBSTFORMAT2_HH
|
||||
|
||||
#include "Common.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
template <typename Types>
|
||||
struct SingleSubstFormat2_4
|
||||
{
|
||||
protected:
|
||||
HBUINT16 format; /* Format identifier--format = 2 */
|
||||
typename Types::template OffsetTo<Coverage>
|
||||
coverage; /* Offset to Coverage table--from
|
||||
* beginning of Substitution table */
|
||||
Array16Of<typename Types::HBGlyphID>
|
||||
substitute; /* Array of substitute
|
||||
* GlyphIDs--ordered by Coverage Index */
|
||||
|
||||
public:
|
||||
DEFINE_SIZE_ARRAY (4 + Types::size, substitute);
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (coverage.sanitize (c, this) && substitute.sanitize (c));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{ return (this+coverage).intersects (glyphs); }
|
||||
|
||||
bool may_have_non_1to1 () const
|
||||
{ return false; }
|
||||
|
||||
void closure (hb_closure_context_t *c) const
|
||||
{
|
||||
auto &cov = this+coverage;
|
||||
auto &glyph_set = c->parent_active_glyphs ();
|
||||
|
||||
if (substitute.len > glyph_set.get_population () * 4)
|
||||
{
|
||||
for (auto g : glyph_set)
|
||||
{
|
||||
unsigned i = cov.get_coverage (g);
|
||||
if (i == NOT_COVERED || i >= substitute.len)
|
||||
continue;
|
||||
c->output->add (substitute.arrayZ[i]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
+ hb_zip (cov, substitute)
|
||||
| hb_filter (glyph_set, hb_first)
|
||||
| hb_map (hb_second)
|
||||
| hb_sink (c->output)
|
||||
;
|
||||
}
|
||||
|
||||
void closure_lookups (hb_closure_lookups_context_t *c) const {}
|
||||
|
||||
void collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
if (unlikely (!(this+coverage).collect_coverage (c->input))) return;
|
||||
+ hb_zip (this+coverage, substitute)
|
||||
| hb_map (hb_second)
|
||||
| hb_sink (c->output)
|
||||
;
|
||||
}
|
||||
|
||||
const Coverage &get_coverage () const { return this+coverage; }
|
||||
|
||||
bool would_apply (hb_would_apply_context_t *c) const
|
||||
{ return c->len == 1 && (this+coverage).get_coverage (c->glyphs[0]) != NOT_COVERED; }
|
||||
|
||||
unsigned
|
||||
get_glyph_alternates (hb_codepoint_t glyph_id,
|
||||
unsigned start_offset,
|
||||
unsigned *alternate_count /* IN/OUT. May be NULL. */,
|
||||
hb_codepoint_t *alternate_glyphs /* OUT. May be NULL. */) const
|
||||
{
|
||||
unsigned int index = (this+coverage).get_coverage (glyph_id);
|
||||
if (likely (index == NOT_COVERED))
|
||||
{
|
||||
if (alternate_count)
|
||||
*alternate_count = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (alternate_count && *alternate_count)
|
||||
{
|
||||
glyph_id = substitute[index];
|
||||
|
||||
*alternate_glyphs = glyph_id;
|
||||
*alternate_count = 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
unsigned int index = (this+coverage).get_coverage (c->buffer->cur().codepoint);
|
||||
if (likely (index == NOT_COVERED)) return_trace (false);
|
||||
|
||||
if (unlikely (index >= substitute.len)) return_trace (false);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->sync_so_far ();
|
||||
c->buffer->message (c->font,
|
||||
"replacing glyph at %u (single substitution)",
|
||||
c->buffer->idx);
|
||||
}
|
||||
|
||||
c->replace_glyph (substitute[index]);
|
||||
|
||||
if (HB_BUFFER_MESSAGE_MORE && c->buffer->messaging ())
|
||||
{
|
||||
c->buffer->message (c->font,
|
||||
"replaced glyph at %u (single substitution)",
|
||||
c->buffer->idx - 1u);
|
||||
}
|
||||
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
template<typename Iterator,
|
||||
hb_requires (hb_is_sorted_source_of (Iterator,
|
||||
hb_codepoint_pair_t))>
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
Iterator it)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
auto substitutes =
|
||||
+ it
|
||||
| hb_map (hb_second)
|
||||
;
|
||||
auto glyphs =
|
||||
+ it
|
||||
| hb_map_retains_sorting (hb_first)
|
||||
;
|
||||
if (unlikely (!c->extend_min (this))) return_trace (false);
|
||||
if (unlikely (!substitute.serialize (c, substitutes))) return_trace (false);
|
||||
if (unlikely (!coverage.serialize_serialize (c, glyphs))) return_trace (false);
|
||||
return_trace (true);
|
||||
}
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{
|
||||
TRACE_SUBSET (this);
|
||||
const hb_set_t &glyphset = *c->plan->glyphset_gsub ();
|
||||
const hb_map_t &glyph_map = *c->plan->glyph_map;
|
||||
|
||||
auto it =
|
||||
+ hb_zip (this+coverage, substitute)
|
||||
| hb_filter (glyphset, hb_first)
|
||||
| hb_filter (glyphset, hb_second)
|
||||
| hb_map_retains_sorting ([&] (hb_pair_t<hb_codepoint_t, const typename Types::HBGlyphID &> p) -> hb_codepoint_pair_t
|
||||
{ return hb_pair (glyph_map[p.first], glyph_map[p.second]); })
|
||||
;
|
||||
|
||||
bool ret = bool (it);
|
||||
SingleSubst_serialize (c->serializer, it);
|
||||
return_trace (ret);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_SINGLESUBSTFORMAT2_HH */
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
#ifndef OT_LAYOUT_GSUB_SUBSTLOOKUP_HH
|
||||
#define OT_LAYOUT_GSUB_SUBSTLOOKUP_HH
|
||||
|
||||
#include "Common.hh"
|
||||
#include "SubstLookupSubTable.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
struct SubstLookup : Lookup
|
||||
{
|
||||
using SubTable = SubstLookupSubTable;
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{ return Lookup::sanitize<SubTable> (c); }
|
||||
|
||||
const SubTable& get_subtable (unsigned int i) const
|
||||
{ return Lookup::get_subtable<SubTable> (i); }
|
||||
|
||||
static inline bool lookup_type_is_reverse (unsigned int lookup_type)
|
||||
{ return lookup_type == SubTable::ReverseChainSingle; }
|
||||
|
||||
bool is_reverse () const
|
||||
{
|
||||
unsigned int type = get_type ();
|
||||
if (unlikely (type == SubTable::Extension))
|
||||
return get_subtable (0).u.extension.is_reverse ();
|
||||
return lookup_type_is_reverse (type);
|
||||
}
|
||||
|
||||
bool may_have_non_1to1 () const
|
||||
{
|
||||
hb_have_non_1to1_context_t c;
|
||||
return dispatch (&c);
|
||||
}
|
||||
|
||||
bool apply (hb_ot_apply_context_t *c) const
|
||||
{
|
||||
TRACE_APPLY (this);
|
||||
return_trace (dispatch (c));
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs) const
|
||||
{
|
||||
hb_intersects_context_t c (glyphs);
|
||||
return dispatch (&c);
|
||||
}
|
||||
|
||||
hb_closure_context_t::return_t closure (hb_closure_context_t *c, unsigned int this_index) const
|
||||
{
|
||||
if (!c->should_visit_lookup (this_index))
|
||||
return hb_closure_context_t::default_return_value ();
|
||||
|
||||
c->set_recurse_func (dispatch_closure_recurse_func);
|
||||
|
||||
hb_closure_context_t::return_t ret = dispatch (c);
|
||||
|
||||
c->flush ();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
hb_closure_lookups_context_t::return_t closure_lookups (hb_closure_lookups_context_t *c, unsigned this_index) const
|
||||
{
|
||||
if (c->is_lookup_visited (this_index))
|
||||
return hb_closure_lookups_context_t::default_return_value ();
|
||||
|
||||
c->set_lookup_visited (this_index);
|
||||
if (!intersects (c->glyphs))
|
||||
{
|
||||
c->set_lookup_inactive (this_index);
|
||||
return hb_closure_lookups_context_t::default_return_value ();
|
||||
}
|
||||
|
||||
hb_closure_lookups_context_t::return_t ret = dispatch (c);
|
||||
return ret;
|
||||
}
|
||||
|
||||
hb_collect_glyphs_context_t::return_t collect_glyphs (hb_collect_glyphs_context_t *c) const
|
||||
{
|
||||
c->set_recurse_func (dispatch_recurse_func<hb_collect_glyphs_context_t>);
|
||||
return dispatch (c);
|
||||
}
|
||||
|
||||
template <typename set_t>
|
||||
void collect_coverage (set_t *glyphs) const
|
||||
{
|
||||
hb_collect_coverage_context_t<set_t> c (glyphs);
|
||||
dispatch (&c);
|
||||
}
|
||||
|
||||
bool would_apply (hb_would_apply_context_t *c,
|
||||
const hb_ot_layout_lookup_accelerator_t *accel) const
|
||||
{
|
||||
if (unlikely (!c->len)) return false;
|
||||
if (!accel->may_have (c->glyphs[0])) return false;
|
||||
return dispatch (c);
|
||||
}
|
||||
|
||||
template<typename Glyphs, typename Substitutes,
|
||||
hb_requires (hb_is_sorted_source_of (Glyphs,
|
||||
const hb_codepoint_t) &&
|
||||
hb_is_source_of (Substitutes,
|
||||
const hb_codepoint_t))>
|
||||
bool serialize_single (hb_serialize_context_t *c,
|
||||
uint32_t lookup_props,
|
||||
Glyphs glyphs,
|
||||
Substitutes substitutes)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!Lookup::serialize (c, SubTable::Single, lookup_props, 1))) return_trace (false);
|
||||
if (c->push<SubTable> ()->u.single.serialize (c, hb_zip (glyphs, substitutes)))
|
||||
{
|
||||
c->add_link (get_subtables<SubTable> ()[0], c->pop_pack ());
|
||||
return_trace (true);
|
||||
}
|
||||
c->pop_discard ();
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
template<typename Iterator,
|
||||
hb_requires (hb_is_sorted_iterator (Iterator))>
|
||||
bool serialize (hb_serialize_context_t *c,
|
||||
uint32_t lookup_props,
|
||||
Iterator it)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!Lookup::serialize (c, SubTable::Multiple, lookup_props, 1))) return_trace (false);
|
||||
if (c->push<SubTable> ()->u.multiple.
|
||||
serialize (c, it))
|
||||
{
|
||||
c->add_link (get_subtables<SubTable> ()[0], c->pop_pack ());
|
||||
return_trace (true);
|
||||
}
|
||||
c->pop_discard ();
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
bool serialize_alternate (hb_serialize_context_t *c,
|
||||
uint32_t lookup_props,
|
||||
hb_sorted_array_t<const HBGlyphID16> glyphs,
|
||||
hb_array_t<const unsigned int> alternate_len_list,
|
||||
hb_array_t<const HBGlyphID16> alternate_glyphs_list)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!Lookup::serialize (c, SubTable::Alternate, lookup_props, 1))) return_trace (false);
|
||||
|
||||
if (c->push<SubTable> ()->u.alternate.
|
||||
serialize (c,
|
||||
glyphs,
|
||||
alternate_len_list,
|
||||
alternate_glyphs_list))
|
||||
{
|
||||
c->add_link (get_subtables<SubTable> ()[0], c->pop_pack ());
|
||||
return_trace (true);
|
||||
}
|
||||
c->pop_discard ();
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
bool serialize_ligature (hb_serialize_context_t *c,
|
||||
uint32_t lookup_props,
|
||||
hb_sorted_array_t<const HBGlyphID16> first_glyphs,
|
||||
hb_array_t<const unsigned int> ligature_per_first_glyph_count_list,
|
||||
hb_array_t<const HBGlyphID16> ligatures_list,
|
||||
hb_array_t<const unsigned int> component_count_list,
|
||||
hb_array_t<const HBGlyphID16> component_list /* Starting from second for each ligature */)
|
||||
{
|
||||
TRACE_SERIALIZE (this);
|
||||
if (unlikely (!Lookup::serialize (c, SubTable::Ligature, lookup_props, 1))) return_trace (false);
|
||||
if (c->push<SubTable> ()->u.ligature.
|
||||
serialize (c,
|
||||
first_glyphs,
|
||||
ligature_per_first_glyph_count_list,
|
||||
ligatures_list,
|
||||
component_count_list,
|
||||
component_list))
|
||||
{
|
||||
c->add_link (get_subtables<SubTable> ()[0], c->pop_pack ());
|
||||
return_trace (true);
|
||||
}
|
||||
c->pop_discard ();
|
||||
return_trace (false);
|
||||
}
|
||||
|
||||
template <typename context_t>
|
||||
static inline typename context_t::return_t dispatch_recurse_func (context_t *c, unsigned int lookup_index);
|
||||
|
||||
static inline typename hb_closure_context_t::return_t closure_glyphs_recurse_func (hb_closure_context_t *c, unsigned lookup_index, hb_set_t *covered_seq_indices, unsigned seq_index, unsigned end_index);
|
||||
|
||||
static inline hb_closure_context_t::return_t dispatch_closure_recurse_func (hb_closure_context_t *c, unsigned lookup_index, hb_set_t *covered_seq_indices, unsigned seq_index, unsigned end_index)
|
||||
{
|
||||
if (!c->should_visit_lookup (lookup_index))
|
||||
return hb_empty_t ();
|
||||
|
||||
hb_closure_context_t::return_t ret = closure_glyphs_recurse_func (c, lookup_index, covered_seq_indices, seq_index, end_index);
|
||||
|
||||
/* While in theory we should flush here, it will cause timeouts because a recursive
|
||||
* lookup can keep growing the glyph set. Skip, and outer loop will retry up to
|
||||
* HB_CLOSURE_MAX_STAGES time, which should be enough for every realistic font. */
|
||||
//c->flush ();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, Ts&&... ds) const
|
||||
{ return Lookup::dispatch<SubTable> (c, std::forward<Ts> (ds)...); }
|
||||
|
||||
bool subset (hb_subset_context_t *c) const
|
||||
{ return Lookup::subset<SubTable> (c); }
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_GSUB_SUBSTLOOKUP_HH */
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
#ifndef OT_LAYOUT_GSUB_SUBSTLOOKUPSUBTABLE_HH
|
||||
#define OT_LAYOUT_GSUB_SUBSTLOOKUPSUBTABLE_HH
|
||||
|
||||
#include "Common.hh"
|
||||
#include "SingleSubst.hh"
|
||||
#include "MultipleSubst.hh"
|
||||
#include "AlternateSubst.hh"
|
||||
#include "LigatureSubst.hh"
|
||||
#include "ContextSubst.hh"
|
||||
#include "ChainContextSubst.hh"
|
||||
#include "ExtensionSubst.hh"
|
||||
#include "ReverseChainSingleSubst.hh"
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
namespace GSUB_impl {
|
||||
|
||||
struct SubstLookupSubTable
|
||||
{
|
||||
friend struct ::OT::Lookup;
|
||||
friend struct SubstLookup;
|
||||
|
||||
protected:
|
||||
union {
|
||||
SingleSubst single;
|
||||
MultipleSubst multiple;
|
||||
AlternateSubst alternate;
|
||||
LigatureSubst ligature;
|
||||
ContextSubst context;
|
||||
ChainContextSubst chainContext;
|
||||
ExtensionSubst extension;
|
||||
ReverseChainSingleSubst reverseChainContextSingle;
|
||||
} u;
|
||||
public:
|
||||
DEFINE_SIZE_MIN (0);
|
||||
|
||||
enum Type {
|
||||
Single = 1,
|
||||
Multiple = 2,
|
||||
Alternate = 3,
|
||||
Ligature = 4,
|
||||
Context = 5,
|
||||
ChainContext = 6,
|
||||
Extension = 7,
|
||||
ReverseChainSingle = 8
|
||||
};
|
||||
|
||||
template <typename context_t, typename ...Ts>
|
||||
typename context_t::return_t dispatch (context_t *c, unsigned int lookup_type, Ts&&... ds) const
|
||||
{
|
||||
TRACE_DISPATCH (this, lookup_type);
|
||||
switch (lookup_type) {
|
||||
case Single: return_trace (u.single.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case Multiple: return_trace (u.multiple.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case Alternate: return_trace (u.alternate.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case Ligature: return_trace (u.ligature.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case Context: return_trace (u.context.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case ChainContext: return_trace (u.chainContext.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case Extension: return_trace (u.extension.dispatch (c, std::forward<Ts> (ds)...));
|
||||
case ReverseChainSingle: return_trace (u.reverseChainContextSingle.dispatch (c, std::forward<Ts> (ds)...));
|
||||
default: return_trace (c->default_return_value ());
|
||||
}
|
||||
}
|
||||
|
||||
bool intersects (const hb_set_t *glyphs, unsigned int lookup_type) const
|
||||
{
|
||||
hb_intersects_context_t c (glyphs);
|
||||
return dispatch (&c, lookup_type);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* HB_OT_LAYOUT_GSUB_SUBSTLOOKUPSUBTABLE_HH */
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright © 2007,2008,2009 Red Hat, Inc.
|
||||
* Copyright © 2010,2012 Google, Inc.
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
* Red Hat Author(s): Behdad Esfahbod
|
||||
* Google Author(s): Behdad Esfahbod, Garret Rieger
|
||||
*/
|
||||
|
||||
#ifndef OT_LAYOUT_TYPES_HH
|
||||
#define OT_LAYOUT_TYPES_HH
|
||||
|
||||
namespace OT {
|
||||
namespace Layout {
|
||||
|
||||
struct SmallTypes {
|
||||
static constexpr unsigned size = 2;
|
||||
using large_int = uint32_t;
|
||||
using HBUINT = HBUINT16;
|
||||
using HBGlyphID = HBGlyphID16;
|
||||
using Offset = Offset16;
|
||||
template <typename Type, typename BaseType=void, bool has_null=true>
|
||||
using OffsetTo = OT::Offset16To<Type, BaseType, has_null>;
|
||||
template <typename Type>
|
||||
using ArrayOf = OT::Array16Of<Type>;
|
||||
template <typename Type>
|
||||
using SortedArrayOf = OT::SortedArray16Of<Type>;
|
||||
};
|
||||
|
||||
struct MediumTypes {
|
||||
static constexpr unsigned size = 3;
|
||||
using large_int = uint64_t;
|
||||
using HBUINT = HBUINT24;
|
||||
using HBGlyphID = HBGlyphID24;
|
||||
using Offset = Offset24;
|
||||
template <typename Type, typename BaseType=void, bool has_null=true>
|
||||
using OffsetTo = OT::Offset24To<Type, BaseType, has_null>;
|
||||
template <typename Type>
|
||||
using ArrayOf = OT::Array24Of<Type>;
|
||||
template <typename Type>
|
||||
using SortedArrayOf = OT::SortedArray24Of<Type>;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* OT_LAYOUT_TYPES_HH */
|
||||
@@ -0,0 +1,346 @@
|
||||
#include "VARC.hh"
|
||||
|
||||
#ifndef HB_NO_VAR_COMPOSITES
|
||||
|
||||
#include "../../../hb-draw.hh"
|
||||
#include "../../../hb-geometry.hh"
|
||||
#include "../../../hb-ot-layout-common.hh"
|
||||
#include "../../../hb-ot-layout-gdef-table.hh"
|
||||
|
||||
namespace OT {
|
||||
|
||||
//namespace Var {
|
||||
|
||||
|
||||
struct hb_transforming_pen_context_t
|
||||
{
|
||||
hb_transform_t transform;
|
||||
hb_draw_funcs_t *dfuncs;
|
||||
void *data;
|
||||
hb_draw_state_t *st;
|
||||
};
|
||||
|
||||
static void
|
||||
hb_transforming_pen_move_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
|
||||
void *data,
|
||||
hb_draw_state_t *st,
|
||||
float to_x, float to_y,
|
||||
void *user_data HB_UNUSED)
|
||||
{
|
||||
hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
|
||||
|
||||
c->transform.transform_point (to_x, to_y);
|
||||
|
||||
c->dfuncs->move_to (c->data, *c->st, to_x, to_y);
|
||||
}
|
||||
|
||||
static void
|
||||
hb_transforming_pen_line_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
|
||||
void *data,
|
||||
hb_draw_state_t *st,
|
||||
float to_x, float to_y,
|
||||
void *user_data HB_UNUSED)
|
||||
{
|
||||
hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
|
||||
|
||||
c->transform.transform_point (to_x, to_y);
|
||||
|
||||
c->dfuncs->line_to (c->data, *c->st, to_x, to_y);
|
||||
}
|
||||
|
||||
static void
|
||||
hb_transforming_pen_quadratic_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
|
||||
void *data,
|
||||
hb_draw_state_t *st,
|
||||
float control_x, float control_y,
|
||||
float to_x, float to_y,
|
||||
void *user_data HB_UNUSED)
|
||||
{
|
||||
hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
|
||||
|
||||
c->transform.transform_point (control_x, control_y);
|
||||
c->transform.transform_point (to_x, to_y);
|
||||
|
||||
c->dfuncs->quadratic_to (c->data, *c->st, control_x, control_y, to_x, to_y);
|
||||
}
|
||||
|
||||
static void
|
||||
hb_transforming_pen_cubic_to (hb_draw_funcs_t *dfuncs HB_UNUSED,
|
||||
void *data,
|
||||
hb_draw_state_t *st,
|
||||
float control1_x, float control1_y,
|
||||
float control2_x, float control2_y,
|
||||
float to_x, float to_y,
|
||||
void *user_data HB_UNUSED)
|
||||
{
|
||||
hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
|
||||
|
||||
c->transform.transform_point (control1_x, control1_y);
|
||||
c->transform.transform_point (control2_x, control2_y);
|
||||
c->transform.transform_point (to_x, to_y);
|
||||
|
||||
c->dfuncs->cubic_to (c->data, *c->st, control1_x, control1_y, control2_x, control2_y, to_x, to_y);
|
||||
}
|
||||
|
||||
static void
|
||||
hb_transforming_pen_close_path (hb_draw_funcs_t *dfuncs HB_UNUSED,
|
||||
void *data,
|
||||
hb_draw_state_t *st,
|
||||
void *user_data HB_UNUSED)
|
||||
{
|
||||
hb_transforming_pen_context_t *c = (hb_transforming_pen_context_t *) data;
|
||||
|
||||
c->dfuncs->close_path (c->data, *c->st);
|
||||
}
|
||||
|
||||
static inline void free_static_transforming_pen_funcs ();
|
||||
|
||||
static struct hb_transforming_pen_funcs_lazy_loader_t : hb_draw_funcs_lazy_loader_t<hb_transforming_pen_funcs_lazy_loader_t>
|
||||
{
|
||||
static hb_draw_funcs_t *create ()
|
||||
{
|
||||
hb_draw_funcs_t *funcs = hb_draw_funcs_create ();
|
||||
|
||||
hb_draw_funcs_set_move_to_func (funcs, hb_transforming_pen_move_to, nullptr, nullptr);
|
||||
hb_draw_funcs_set_line_to_func (funcs, hb_transforming_pen_line_to, nullptr, nullptr);
|
||||
hb_draw_funcs_set_quadratic_to_func (funcs, hb_transforming_pen_quadratic_to, nullptr, nullptr);
|
||||
hb_draw_funcs_set_cubic_to_func (funcs, hb_transforming_pen_cubic_to, nullptr, nullptr);
|
||||
hb_draw_funcs_set_close_path_func (funcs, hb_transforming_pen_close_path, nullptr, nullptr);
|
||||
|
||||
hb_draw_funcs_make_immutable (funcs);
|
||||
|
||||
hb_atexit (free_static_transforming_pen_funcs);
|
||||
|
||||
return funcs;
|
||||
}
|
||||
} static_transforming_pen_funcs;
|
||||
|
||||
static inline
|
||||
void free_static_transforming_pen_funcs ()
|
||||
{
|
||||
static_transforming_pen_funcs.free_instance ();
|
||||
}
|
||||
|
||||
static hb_draw_funcs_t *
|
||||
hb_transforming_pen_get_funcs ()
|
||||
{
|
||||
return static_transforming_pen_funcs.get_unconst ();
|
||||
}
|
||||
|
||||
|
||||
hb_ubytes_t
|
||||
VarComponent::get_path_at (hb_font_t *font,
|
||||
hb_codepoint_t parent_gid,
|
||||
hb_draw_session_t &draw_session,
|
||||
hb_array_t<const int> coords,
|
||||
hb_ubytes_t total_record,
|
||||
hb_set_t *visited,
|
||||
signed *edges_left,
|
||||
signed depth_left,
|
||||
VarRegionList::cache_t *cache) const
|
||||
{
|
||||
const unsigned char *end = total_record.arrayZ + total_record.length;
|
||||
const unsigned char *record = total_record.arrayZ;
|
||||
|
||||
auto &VARC = *font->face->table.VARC;
|
||||
auto &varStore = &VARC+VARC.varStore;
|
||||
auto instancer = MultiItemVarStoreInstancer(&varStore, nullptr, coords, cache);
|
||||
|
||||
#define READ_UINT32VAR(name) \
|
||||
HB_STMT_START { \
|
||||
if (unlikely (unsigned (end - record) < HBUINT32VAR::min_size)) return hb_ubytes_t (); \
|
||||
hb_barrier (); \
|
||||
auto &varint = * (const HBUINT32VAR *) record; \
|
||||
unsigned size = varint.get_size (); \
|
||||
if (unlikely (unsigned (end - record) < size)) return hb_ubytes_t (); \
|
||||
name = (uint32_t) varint; \
|
||||
record += size; \
|
||||
} HB_STMT_END
|
||||
|
||||
uint32_t flags;
|
||||
READ_UINT32VAR (flags);
|
||||
|
||||
// gid
|
||||
|
||||
hb_codepoint_t gid = 0;
|
||||
if (flags & (unsigned) flags_t::GID_IS_24BIT)
|
||||
{
|
||||
if (unlikely (unsigned (end - record) < HBGlyphID24::static_size))
|
||||
return hb_ubytes_t ();
|
||||
hb_barrier ();
|
||||
gid = * (const HBGlyphID24 *) record;
|
||||
record += HBGlyphID24::static_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (unlikely (unsigned (end - record) < HBGlyphID16::static_size))
|
||||
return hb_ubytes_t ();
|
||||
hb_barrier ();
|
||||
gid = * (const HBGlyphID16 *) record;
|
||||
record += HBGlyphID16::static_size;
|
||||
}
|
||||
|
||||
// Condition
|
||||
bool show = true;
|
||||
if (flags & (unsigned) flags_t::HAVE_CONDITION)
|
||||
{
|
||||
unsigned conditionIndex;
|
||||
READ_UINT32VAR (conditionIndex);
|
||||
const auto &condition = (&VARC+VARC.conditionList)[conditionIndex];
|
||||
show = condition.evaluate (coords.arrayZ, coords.length, &instancer);
|
||||
}
|
||||
|
||||
// Axis values
|
||||
|
||||
hb_vector_t<unsigned> axisIndices;
|
||||
hb_vector_t<float> axisValues;
|
||||
if (flags & (unsigned) flags_t::HAVE_AXES)
|
||||
{
|
||||
unsigned axisIndicesIndex;
|
||||
READ_UINT32VAR (axisIndicesIndex);
|
||||
axisIndices = (&VARC+VARC.axisIndicesList)[axisIndicesIndex];
|
||||
axisValues.resize (axisIndices.length);
|
||||
const HBUINT8 *p = (const HBUINT8 *) record;
|
||||
TupleValues::decompile (p, axisValues, (const HBUINT8 *) end);
|
||||
record += (const unsigned char *) p - record;
|
||||
}
|
||||
|
||||
// Apply variations if any
|
||||
if (flags & (unsigned) flags_t::AXIS_VALUES_HAVE_VARIATION)
|
||||
{
|
||||
uint32_t axisValuesVarIdx;
|
||||
READ_UINT32VAR (axisValuesVarIdx);
|
||||
if (show && coords && !axisValues.in_error ())
|
||||
varStore.get_delta (axisValuesVarIdx, coords, axisValues.as_array (), cache);
|
||||
}
|
||||
|
||||
auto component_coords = coords;
|
||||
/* Copying coords is expensive; so we have put an arbitrary
|
||||
* limit on the max number of coords for now. */
|
||||
if ((flags & (unsigned) flags_t::RESET_UNSPECIFIED_AXES) ||
|
||||
coords.length > HB_VAR_COMPOSITE_MAX_AXES)
|
||||
component_coords = hb_array<int> (font->coords, font->num_coords);
|
||||
|
||||
// Transform
|
||||
|
||||
uint32_t transformVarIdx = VarIdx::NO_VARIATION;
|
||||
if (flags & (unsigned) flags_t::TRANSFORM_HAS_VARIATION)
|
||||
READ_UINT32VAR (transformVarIdx);
|
||||
|
||||
#define PROCESS_TRANSFORM_COMPONENTS \
|
||||
HB_STMT_START { \
|
||||
PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TRANSLATE_X, translateX); \
|
||||
PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TRANSLATE_Y, translateY); \
|
||||
PROCESS_TRANSFORM_COMPONENT (F4DOT12, HAVE_ROTATION, rotation); \
|
||||
PROCESS_TRANSFORM_COMPONENT (F6DOT10, HAVE_SCALE_X, scaleX); \
|
||||
PROCESS_TRANSFORM_COMPONENT (F6DOT10, HAVE_SCALE_Y, scaleY); \
|
||||
PROCESS_TRANSFORM_COMPONENT (F4DOT12, HAVE_SKEW_X, skewX); \
|
||||
PROCESS_TRANSFORM_COMPONENT (F4DOT12, HAVE_SKEW_Y, skewY); \
|
||||
PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TCENTER_X, tCenterX); \
|
||||
PROCESS_TRANSFORM_COMPONENT (FWORD, HAVE_TCENTER_Y, tCenterY); \
|
||||
} HB_STMT_END
|
||||
|
||||
hb_transform_decomposed_t transform;
|
||||
|
||||
// Read transform components
|
||||
#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
|
||||
if (flags & (unsigned) flags_t::flag) \
|
||||
{ \
|
||||
static_assert (type::static_size == HBINT16::static_size, ""); \
|
||||
if (unlikely (unsigned (end - record) < HBINT16::static_size)) \
|
||||
return hb_ubytes_t (); \
|
||||
hb_barrier (); \
|
||||
transform.name = * (const HBINT16 *) record; \
|
||||
record += HBINT16::static_size; \
|
||||
}
|
||||
PROCESS_TRANSFORM_COMPONENTS;
|
||||
#undef PROCESS_TRANSFORM_COMPONENT
|
||||
|
||||
// Read reserved records
|
||||
unsigned i = flags & (unsigned) flags_t::RESERVED_MASK;
|
||||
while (i)
|
||||
{
|
||||
HB_UNUSED uint32_t discard;
|
||||
READ_UINT32VAR (discard);
|
||||
i &= i - 1;
|
||||
}
|
||||
|
||||
/* Parsing is over now. */
|
||||
|
||||
if (show)
|
||||
{
|
||||
// Only use coord_setter if there's actually any axis overrides.
|
||||
coord_setter_t coord_setter (axisIndices ? component_coords : hb_array<int> ());
|
||||
// Go backwards, to reduce coord_setter vector reallocations.
|
||||
for (unsigned i = axisIndices.length; i; i--)
|
||||
coord_setter[axisIndices[i - 1]] = axisValues[i - 1];
|
||||
if (axisIndices)
|
||||
component_coords = coord_setter.get_coords ();
|
||||
|
||||
// Apply transform variations if any
|
||||
if (transformVarIdx != VarIdx::NO_VARIATION && coords)
|
||||
{
|
||||
float transformValues[9];
|
||||
unsigned numTransformValues = 0;
|
||||
#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
|
||||
if (flags & (unsigned) flags_t::flag) \
|
||||
transformValues[numTransformValues++] = transform.name;
|
||||
PROCESS_TRANSFORM_COMPONENTS;
|
||||
#undef PROCESS_TRANSFORM_COMPONENT
|
||||
varStore.get_delta (transformVarIdx, coords, hb_array (transformValues, numTransformValues), cache);
|
||||
numTransformValues = 0;
|
||||
#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
|
||||
if (flags & (unsigned) flags_t::flag) \
|
||||
transform.name = transformValues[numTransformValues++];
|
||||
PROCESS_TRANSFORM_COMPONENTS;
|
||||
#undef PROCESS_TRANSFORM_COMPONENT
|
||||
}
|
||||
|
||||
// Divide them by their divisors
|
||||
#define PROCESS_TRANSFORM_COMPONENT(type, flag, name) \
|
||||
if (flags & (unsigned) flags_t::flag) \
|
||||
{ \
|
||||
HBINT16 int_v; \
|
||||
int_v = roundf (transform.name); \
|
||||
type typed_v = * (const type *) &int_v; \
|
||||
float float_v = (float) typed_v; \
|
||||
transform.name = float_v; \
|
||||
}
|
||||
PROCESS_TRANSFORM_COMPONENTS;
|
||||
#undef PROCESS_TRANSFORM_COMPONENT
|
||||
|
||||
if (!(flags & (unsigned) flags_t::HAVE_SCALE_Y))
|
||||
transform.scaleY = transform.scaleX;
|
||||
|
||||
// Scale the transform by the font's scale
|
||||
float x_scale = font->x_multf;
|
||||
float y_scale = font->y_multf;
|
||||
transform.translateX *= x_scale;
|
||||
transform.translateY *= y_scale;
|
||||
transform.tCenterX *= x_scale;
|
||||
transform.tCenterY *= y_scale;
|
||||
|
||||
// Build a transforming pen to apply the transform.
|
||||
hb_draw_funcs_t *transformer_funcs = hb_transforming_pen_get_funcs ();
|
||||
hb_transforming_pen_context_t context {transform.to_transform (),
|
||||
draw_session.funcs,
|
||||
draw_session.draw_data,
|
||||
&draw_session.st};
|
||||
hb_draw_session_t transformer_session {transformer_funcs, &context};
|
||||
|
||||
VARC.get_path_at (font, gid,
|
||||
transformer_session, component_coords,
|
||||
parent_gid,
|
||||
visited, edges_left, depth_left - 1);
|
||||
}
|
||||
|
||||
#undef PROCESS_TRANSFORM_COMPONENTS
|
||||
#undef READ_UINT32VAR
|
||||
|
||||
return hb_ubytes_t (record, end - record);
|
||||
}
|
||||
|
||||
//} // namespace Var
|
||||
} // namespace OT
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,193 @@
|
||||
#ifndef OT_VAR_VARC_VARC_HH
|
||||
#define OT_VAR_VARC_VARC_HH
|
||||
|
||||
#include "../../../hb-ot-layout-common.hh"
|
||||
#include "../../../hb-ot-glyf-table.hh"
|
||||
#include "../../../hb-ot-cff2-table.hh"
|
||||
#include "../../../hb-ot-cff1-table.hh"
|
||||
|
||||
#include "coord-setter.hh"
|
||||
|
||||
namespace OT {
|
||||
|
||||
//namespace Var {
|
||||
|
||||
/*
|
||||
* VARC -- Variable Composites
|
||||
* https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md
|
||||
*/
|
||||
|
||||
#ifndef HB_NO_VAR_COMPOSITES
|
||||
|
||||
struct VarComponent
|
||||
{
|
||||
enum class flags_t : uint32_t
|
||||
{
|
||||
RESET_UNSPECIFIED_AXES = 1u << 0,
|
||||
HAVE_AXES = 1u << 1,
|
||||
AXIS_VALUES_HAVE_VARIATION = 1u << 2,
|
||||
TRANSFORM_HAS_VARIATION = 1u << 3,
|
||||
HAVE_TRANSLATE_X = 1u << 4,
|
||||
HAVE_TRANSLATE_Y = 1u << 5,
|
||||
HAVE_ROTATION = 1u << 6,
|
||||
HAVE_CONDITION = 1u << 7,
|
||||
HAVE_SCALE_X = 1u << 8,
|
||||
HAVE_SCALE_Y = 1u << 9,
|
||||
HAVE_TCENTER_X = 1u << 10,
|
||||
HAVE_TCENTER_Y = 1u << 11,
|
||||
GID_IS_24BIT = 1u << 12,
|
||||
HAVE_SKEW_X = 1u << 13,
|
||||
HAVE_SKEW_Y = 1u << 14,
|
||||
RESERVED_MASK = ~((1u << 15) - 1),
|
||||
};
|
||||
|
||||
HB_INTERNAL hb_ubytes_t
|
||||
get_path_at (hb_font_t *font,
|
||||
hb_codepoint_t parent_gid,
|
||||
hb_draw_session_t &draw_session,
|
||||
hb_array_t<const int> coords,
|
||||
hb_ubytes_t record,
|
||||
hb_set_t *visited,
|
||||
signed *edges_left,
|
||||
signed depth_left,
|
||||
VarRegionList::cache_t *cache = nullptr) const;
|
||||
};
|
||||
|
||||
struct VarCompositeGlyph
|
||||
{
|
||||
static void
|
||||
get_path_at (hb_font_t *font,
|
||||
hb_codepoint_t glyph,
|
||||
hb_draw_session_t &draw_session,
|
||||
hb_array_t<const int> coords,
|
||||
hb_ubytes_t record,
|
||||
hb_set_t *visited,
|
||||
signed *edges_left,
|
||||
signed depth_left,
|
||||
VarRegionList::cache_t *cache = nullptr)
|
||||
{
|
||||
while (record)
|
||||
{
|
||||
const VarComponent &comp = * (const VarComponent *) (record.arrayZ);
|
||||
record = comp.get_path_at (font, glyph,
|
||||
draw_session, coords,
|
||||
record,
|
||||
visited, edges_left, depth_left, cache);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
HB_MARK_AS_FLAG_T (VarComponent::flags_t);
|
||||
|
||||
struct VARC
|
||||
{
|
||||
friend struct VarComponent;
|
||||
|
||||
static constexpr hb_tag_t tableTag = HB_TAG ('V', 'A', 'R', 'C');
|
||||
|
||||
bool
|
||||
get_path_at (hb_font_t *font,
|
||||
hb_codepoint_t glyph,
|
||||
hb_draw_session_t &draw_session,
|
||||
hb_array_t<const int> coords,
|
||||
hb_codepoint_t parent_glyph = HB_CODEPOINT_INVALID,
|
||||
hb_set_t *visited = nullptr,
|
||||
signed *edges_left = nullptr,
|
||||
signed depth_left = HB_MAX_NESTING_LEVEL) const
|
||||
{
|
||||
hb_set_t stack_set;
|
||||
if (visited == nullptr)
|
||||
visited = &stack_set;
|
||||
signed stack_edges = HB_MAX_GRAPH_EDGE_COUNT;
|
||||
if (edges_left == nullptr)
|
||||
edges_left = &stack_edges;
|
||||
|
||||
// Don't recurse on the same glyph.
|
||||
unsigned idx = glyph == parent_glyph ?
|
||||
NOT_COVERED :
|
||||
(this+coverage).get_coverage (glyph);
|
||||
if (idx == NOT_COVERED)
|
||||
{
|
||||
if (!font->face->table.glyf->get_path_at (font, glyph, draw_session, coords))
|
||||
#ifndef HB_NO_CFF
|
||||
if (!font->face->table.cff2->get_path_at (font, glyph, draw_session, coords))
|
||||
if (!font->face->table.cff1->get_path (font, glyph, draw_session)) // Doesn't have variations
|
||||
#endif
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (depth_left <= 0)
|
||||
return true;
|
||||
|
||||
if (*edges_left <= 0)
|
||||
return true;
|
||||
(*edges_left)--;
|
||||
|
||||
if (visited->has (glyph) || visited->in_error ())
|
||||
return true;
|
||||
visited->add (glyph);
|
||||
|
||||
hb_ubytes_t record = (this+glyphRecords)[idx];
|
||||
|
||||
VarRegionList::cache_t *cache = record.length >= 64 ? // Heuristic
|
||||
(this+varStore).create_cache ()
|
||||
: nullptr;
|
||||
|
||||
VarCompositeGlyph::get_path_at (font, glyph,
|
||||
draw_session, coords,
|
||||
record,
|
||||
visited, edges_left, depth_left,
|
||||
cache);
|
||||
|
||||
(this+varStore).destroy_cache (cache);
|
||||
|
||||
visited->del (glyph);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
get_path (hb_font_t *font, hb_codepoint_t gid, hb_draw_session_t &draw_session) const
|
||||
{ return get_path_at (font, gid, draw_session, hb_array (font->coords, font->num_coords)); }
|
||||
|
||||
bool paint_glyph (hb_font_t *font, hb_codepoint_t gid, hb_paint_funcs_t *funcs, void *data, hb_color_t foreground) const
|
||||
{
|
||||
funcs->push_clip_glyph (data, gid, font);
|
||||
funcs->color (data, true, foreground);
|
||||
funcs->pop_clip (data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sanitize (hb_sanitize_context_t *c) const
|
||||
{
|
||||
TRACE_SANITIZE (this);
|
||||
return_trace (version.sanitize (c) &&
|
||||
hb_barrier () &&
|
||||
version.major == 1 &&
|
||||
coverage.sanitize (c, this) &&
|
||||
varStore.sanitize (c, this) &&
|
||||
conditionList.sanitize (c, this) &&
|
||||
axisIndicesList.sanitize (c, this) &&
|
||||
glyphRecords.sanitize (c, this));
|
||||
}
|
||||
|
||||
protected:
|
||||
FixedVersion<> version; /* Version identifier */
|
||||
Offset32To<Coverage> coverage;
|
||||
Offset32To<MultiItemVariationStore> varStore;
|
||||
Offset32To<ConditionList> conditionList;
|
||||
Offset32To<TupleList> axisIndicesList;
|
||||
Offset32To<CFF2Index/*Of<VarCompositeGlyph>*/> glyphRecords;
|
||||
public:
|
||||
DEFINE_SIZE_STATIC (24);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
#endif /* OT_VAR_VARC_VARC_HH */
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef OT_VAR_VARC_COORD_SETTER_HH
|
||||
#define OT_VAR_VARC_COORD_SETTER_HH
|
||||
|
||||
|
||||
#include "../../../hb.hh"
|
||||
|
||||
|
||||
namespace OT {
|
||||
//namespace Var {
|
||||
|
||||
|
||||
struct coord_setter_t
|
||||
{
|
||||
coord_setter_t (hb_array_t<const int> coords) :
|
||||
coords (coords) {}
|
||||
|
||||
int& operator [] (unsigned idx)
|
||||
{
|
||||
if (unlikely (idx >= HB_VAR_COMPOSITE_MAX_AXES))
|
||||
return Crap(int);
|
||||
if (coords.length < idx + 1)
|
||||
coords.resize (idx + 1);
|
||||
return coords[idx];
|
||||
}
|
||||
|
||||
hb_array_t<int> get_coords ()
|
||||
{ return coords.as_array (); }
|
||||
|
||||
hb_vector_t<int> coords;
|
||||
};
|
||||
|
||||
|
||||
//} // namespace Var
|
||||
|
||||
} // namespace OT
|
||||
|
||||
#endif /* OT_VAR_VARC_COORD_SETTER_HH */
|
||||
@@ -0,0 +1,435 @@
|
||||
#ifndef OT_GLYF_COMPOSITEGLYPH_HH
|
||||
#define OT_GLYF_COMPOSITEGLYPH_HH
|
||||
|
||||
|
||||
#include "../../hb-open-type.hh"
|
||||
#include "composite-iter.hh"
|
||||
|
||||
|
||||
namespace OT {
|
||||
namespace glyf_impl {
|
||||
|
||||
|
||||
struct CompositeGlyphRecord
|
||||
{
|
||||
protected:
|
||||
enum composite_glyph_flag_t
|
||||
{
|
||||
ARG_1_AND_2_ARE_WORDS = 0x0001,
|
||||
ARGS_ARE_XY_VALUES = 0x0002,
|
||||
ROUND_XY_TO_GRID = 0x0004,
|
||||
WE_HAVE_A_SCALE = 0x0008,
|
||||
MORE_COMPONENTS = 0x0020,
|
||||
WE_HAVE_AN_X_AND_Y_SCALE = 0x0040,
|
||||
WE_HAVE_A_TWO_BY_TWO = 0x0080,
|
||||
WE_HAVE_INSTRUCTIONS = 0x0100,
|
||||
USE_MY_METRICS = 0x0200,
|
||||
OVERLAP_COMPOUND = 0x0400,
|
||||
SCALED_COMPONENT_OFFSET = 0x0800,
|
||||
UNSCALED_COMPONENT_OFFSET = 0x1000,
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
GID_IS_24BIT = 0x2000
|
||||
#endif
|
||||
};
|
||||
|
||||
public:
|
||||
unsigned int get_size () const
|
||||
{
|
||||
unsigned int size = min_size;
|
||||
/* glyphIndex is 24bit instead of 16bit */
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
if (flags & GID_IS_24BIT) size += HBGlyphID24::static_size - HBGlyphID16::static_size;
|
||||
#endif
|
||||
/* arg1 and 2 are int16 */
|
||||
if (flags & ARG_1_AND_2_ARE_WORDS) size += 4;
|
||||
/* arg1 and 2 are int8 */
|
||||
else size += 2;
|
||||
|
||||
/* One x 16 bit (scale) */
|
||||
if (flags & WE_HAVE_A_SCALE) size += 2;
|
||||
/* Two x 16 bit (xscale, yscale) */
|
||||
else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) size += 4;
|
||||
/* Four x 16 bit (xscale, scale01, scale10, yscale) */
|
||||
else if (flags & WE_HAVE_A_TWO_BY_TWO) size += 8;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void drop_instructions_flag () { flags = (uint16_t) flags & ~WE_HAVE_INSTRUCTIONS; }
|
||||
void set_overlaps_flag ()
|
||||
{
|
||||
flags = (uint16_t) flags | OVERLAP_COMPOUND;
|
||||
}
|
||||
|
||||
bool has_instructions () const { return flags & WE_HAVE_INSTRUCTIONS; }
|
||||
|
||||
bool has_more () const { return flags & MORE_COMPONENTS; }
|
||||
bool is_use_my_metrics () const { return flags & USE_MY_METRICS; }
|
||||
bool is_anchored () const { return !(flags & ARGS_ARE_XY_VALUES); }
|
||||
void get_anchor_points (unsigned int &point1, unsigned int &point2) const
|
||||
{
|
||||
const auto *p = &StructAfter<const HBUINT8> (flags);
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
if (flags & GID_IS_24BIT)
|
||||
p += HBGlyphID24::static_size;
|
||||
else
|
||||
#endif
|
||||
p += HBGlyphID16::static_size;
|
||||
if (flags & ARG_1_AND_2_ARE_WORDS)
|
||||
{
|
||||
point1 = ((const HBUINT16 *) p)[0];
|
||||
point2 = ((const HBUINT16 *) p)[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
point1 = p[0];
|
||||
point2 = p[1];
|
||||
}
|
||||
}
|
||||
|
||||
static void transform (const float (&matrix)[4],
|
||||
hb_array_t<contour_point_t> points)
|
||||
{
|
||||
if (matrix[0] != 1.f || matrix[1] != 0.f ||
|
||||
matrix[2] != 0.f || matrix[3] != 1.f)
|
||||
for (auto &point : points)
|
||||
point.transform (matrix);
|
||||
}
|
||||
|
||||
static void translate (const contour_point_t &trans,
|
||||
hb_array_t<contour_point_t> points)
|
||||
{
|
||||
if (HB_OPTIMIZE_SIZE_VAL)
|
||||
{
|
||||
if (trans.x != 0.f || trans.y != 0.f)
|
||||
for (auto &point : points)
|
||||
point.translate (trans);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (trans.x != 0.f && trans.y != 0.f)
|
||||
for (auto &point : points)
|
||||
point.translate (trans);
|
||||
else
|
||||
{
|
||||
if (trans.x != 0.f)
|
||||
for (auto &point : points)
|
||||
point.x += trans.x;
|
||||
else if (trans.y != 0.f)
|
||||
for (auto &point : points)
|
||||
point.y += trans.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void transform_points (hb_array_t<contour_point_t> points,
|
||||
const float (&matrix)[4],
|
||||
const contour_point_t &trans) const
|
||||
{
|
||||
if (scaled_offsets ())
|
||||
{
|
||||
translate (trans, points);
|
||||
transform (matrix, points);
|
||||
}
|
||||
else
|
||||
{
|
||||
transform (matrix, points);
|
||||
translate (trans, points);
|
||||
}
|
||||
}
|
||||
|
||||
bool get_points (contour_point_vector_t &points) const
|
||||
{
|
||||
float matrix[4];
|
||||
contour_point_t trans;
|
||||
get_transformation (matrix, trans);
|
||||
if (unlikely (!points.alloc (points.length + 4))) return false; // For phantom points
|
||||
points.push (trans);
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned compile_with_point (const contour_point_t &point,
|
||||
char *out) const
|
||||
{
|
||||
const HBINT8 *p = &StructAfter<const HBINT8> (flags);
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
if (flags & GID_IS_24BIT)
|
||||
p += HBGlyphID24::static_size;
|
||||
else
|
||||
#endif
|
||||
p += HBGlyphID16::static_size;
|
||||
|
||||
unsigned len = get_size ();
|
||||
unsigned len_before_val = (const char *)p - (const char *)this;
|
||||
if (flags & ARG_1_AND_2_ARE_WORDS)
|
||||
{
|
||||
// no overflow, copy value
|
||||
hb_memcpy (out, this, len);
|
||||
|
||||
HBINT16 *o = reinterpret_cast<HBINT16 *> (out + len_before_val);
|
||||
o[0] = roundf (point.x);
|
||||
o[1] = roundf (point.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
int new_x = roundf (point.x);
|
||||
int new_y = roundf (point.y);
|
||||
if (new_x <= 127 && new_x >= -128 &&
|
||||
new_y <= 127 && new_y >= -128)
|
||||
{
|
||||
hb_memcpy (out, this, len);
|
||||
HBINT8 *o = reinterpret_cast<HBINT8 *> (out + len_before_val);
|
||||
o[0] = new_x;
|
||||
o[1] = new_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
// new point value has an int8 overflow
|
||||
hb_memcpy (out, this, len_before_val);
|
||||
|
||||
//update flags
|
||||
CompositeGlyphRecord *o = reinterpret_cast<CompositeGlyphRecord *> (out);
|
||||
o->flags = flags | ARG_1_AND_2_ARE_WORDS;
|
||||
out += len_before_val;
|
||||
|
||||
HBINT16 new_value;
|
||||
new_value = new_x;
|
||||
hb_memcpy (out, &new_value, HBINT16::static_size);
|
||||
out += HBINT16::static_size;
|
||||
|
||||
new_value = new_y;
|
||||
hb_memcpy (out, &new_value, HBINT16::static_size);
|
||||
out += HBINT16::static_size;
|
||||
|
||||
hb_memcpy (out, p+2, len - len_before_val - 2);
|
||||
len += 2;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool scaled_offsets () const
|
||||
{ return (flags & (SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET)) == SCALED_COMPONENT_OFFSET; }
|
||||
|
||||
public:
|
||||
bool get_transformation (float (&matrix)[4], contour_point_t &trans) const
|
||||
{
|
||||
matrix[0] = matrix[3] = 1.f;
|
||||
matrix[1] = matrix[2] = 0.f;
|
||||
|
||||
const auto *p = &StructAfter<const HBINT8> (flags);
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
if (flags & GID_IS_24BIT)
|
||||
p += HBGlyphID24::static_size;
|
||||
else
|
||||
#endif
|
||||
p += HBGlyphID16::static_size;
|
||||
int tx, ty;
|
||||
if (flags & ARG_1_AND_2_ARE_WORDS)
|
||||
{
|
||||
tx = *(const HBINT16 *) p;
|
||||
p += HBINT16::static_size;
|
||||
ty = *(const HBINT16 *) p;
|
||||
p += HBINT16::static_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
tx = *p++;
|
||||
ty = *p++;
|
||||
}
|
||||
if (is_anchored ()) tx = ty = 0;
|
||||
|
||||
/* set is_end_point flag to true, used by IUP delta optimization */
|
||||
trans.init ((float) tx, (float) ty, true);
|
||||
|
||||
{
|
||||
const F2DOT14 *points = (const F2DOT14 *) p;
|
||||
if (flags & WE_HAVE_A_SCALE)
|
||||
{
|
||||
matrix[0] = matrix[3] = points[0].to_float ();
|
||||
return true;
|
||||
}
|
||||
else if (flags & WE_HAVE_AN_X_AND_Y_SCALE)
|
||||
{
|
||||
matrix[0] = points[0].to_float ();
|
||||
matrix[3] = points[1].to_float ();
|
||||
return true;
|
||||
}
|
||||
else if (flags & WE_HAVE_A_TWO_BY_TWO)
|
||||
{
|
||||
matrix[0] = points[0].to_float ();
|
||||
matrix[1] = points[1].to_float ();
|
||||
matrix[2] = points[2].to_float ();
|
||||
matrix[3] = points[3].to_float ();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return tx || ty;
|
||||
}
|
||||
|
||||
hb_codepoint_t get_gid () const
|
||||
{
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
if (flags & GID_IS_24BIT)
|
||||
return StructAfter<const HBGlyphID24> (flags);
|
||||
else
|
||||
#endif
|
||||
return StructAfter<const HBGlyphID16> (flags);
|
||||
}
|
||||
void set_gid (hb_codepoint_t gid)
|
||||
{
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
if (flags & GID_IS_24BIT)
|
||||
StructAfter<HBGlyphID24> (flags) = gid;
|
||||
else
|
||||
#endif
|
||||
/* TODO assert? */
|
||||
StructAfter<HBGlyphID16> (flags) = gid;
|
||||
}
|
||||
|
||||
#ifndef HB_NO_BEYOND_64K
|
||||
void lower_gid_24_to_16 ()
|
||||
{
|
||||
hb_codepoint_t gid = get_gid ();
|
||||
if (!(flags & GID_IS_24BIT) || gid > 0xFFFFu)
|
||||
return;
|
||||
|
||||
/* Lower the flag and move the rest of the struct down. */
|
||||
|
||||
unsigned size = get_size ();
|
||||
char *end = (char *) this + size;
|
||||
char *p = &StructAfter<char> (flags);
|
||||
p += HBGlyphID24::static_size;
|
||||
|
||||
flags = flags & ~GID_IS_24BIT;
|
||||
set_gid (gid);
|
||||
|
||||
memmove (p - HBGlyphID24::static_size + HBGlyphID16::static_size, p, end - p);
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
HBUINT16 flags;
|
||||
HBUINT24 pad;
|
||||
public:
|
||||
DEFINE_SIZE_MIN (4);
|
||||
};
|
||||
|
||||
using composite_iter_t = composite_iter_tmpl<CompositeGlyphRecord>;
|
||||
|
||||
struct CompositeGlyph
|
||||
{
|
||||
const GlyphHeader &header;
|
||||
hb_bytes_t bytes;
|
||||
CompositeGlyph (const GlyphHeader &header_, hb_bytes_t bytes_) :
|
||||
header (header_), bytes (bytes_) {}
|
||||
|
||||
composite_iter_t iter () const
|
||||
{ return composite_iter_t (bytes, &StructAfter<CompositeGlyphRecord, GlyphHeader> (header)); }
|
||||
|
||||
unsigned int instructions_length (hb_bytes_t bytes) const
|
||||
{
|
||||
unsigned int start = bytes.length;
|
||||
unsigned int end = bytes.length;
|
||||
const CompositeGlyphRecord *last = nullptr;
|
||||
for (auto &item : iter ())
|
||||
last = &item;
|
||||
if (unlikely (!last)) return 0;
|
||||
|
||||
if (last->has_instructions ())
|
||||
start = (char *) last - &bytes + last->get_size ();
|
||||
if (unlikely (start > end)) return 0;
|
||||
return end - start;
|
||||
}
|
||||
|
||||
/* Trimming for composites not implemented.
|
||||
* If removing hints it falls out of that. */
|
||||
const hb_bytes_t trim_padding () const { return bytes; }
|
||||
|
||||
void drop_hints ()
|
||||
{
|
||||
for (const auto &_ : iter ())
|
||||
const_cast<CompositeGlyphRecord &> (_).drop_instructions_flag ();
|
||||
}
|
||||
|
||||
/* Chop instructions off the end */
|
||||
void drop_hints_bytes (hb_bytes_t &dest_start) const
|
||||
{ dest_start = bytes.sub_array (0, bytes.length - instructions_length (bytes)); }
|
||||
|
||||
void set_overlaps_flag ()
|
||||
{
|
||||
CompositeGlyphRecord& glyph_chain = const_cast<CompositeGlyphRecord &> (
|
||||
StructAfter<CompositeGlyphRecord, GlyphHeader> (header));
|
||||
if (!bytes.check_range(&glyph_chain, CompositeGlyphRecord::min_size))
|
||||
return;
|
||||
glyph_chain.set_overlaps_flag ();
|
||||
}
|
||||
|
||||
bool compile_bytes_with_deltas (const hb_bytes_t &source_bytes,
|
||||
const contour_point_vector_t &points_with_deltas,
|
||||
hb_bytes_t &dest_bytes /* OUT */)
|
||||
{
|
||||
if (source_bytes.length <= GlyphHeader::static_size ||
|
||||
header.numberOfContours != -1)
|
||||
{
|
||||
dest_bytes = hb_bytes_t ();
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned source_len = source_bytes.length - GlyphHeader::static_size;
|
||||
|
||||
/* try to allocate more memories than source glyph bytes
|
||||
* in case that there might be an overflow for int8 value
|
||||
* and we would need to use int16 instead */
|
||||
char *o = (char *) hb_calloc (source_len * 2, sizeof (char));
|
||||
if (unlikely (!o)) return false;
|
||||
|
||||
const CompositeGlyphRecord *c = reinterpret_cast<const CompositeGlyphRecord *> (source_bytes.arrayZ + GlyphHeader::static_size);
|
||||
auto it = composite_iter_t (hb_bytes_t ((const char *)c, source_len), c);
|
||||
|
||||
char *p = o;
|
||||
unsigned i = 0, source_comp_len = 0;
|
||||
for (const auto &component : it)
|
||||
{
|
||||
/* last 4 points in points_with_deltas are phantom points and should not be included */
|
||||
if (i >= points_with_deltas.length - 4) {
|
||||
hb_free (o);
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned comp_len = component.get_size ();
|
||||
if (component.is_anchored ())
|
||||
{
|
||||
hb_memcpy (p, &component, comp_len);
|
||||
p += comp_len;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned new_len = component.compile_with_point (points_with_deltas[i], p);
|
||||
p += new_len;
|
||||
}
|
||||
i++;
|
||||
source_comp_len += comp_len;
|
||||
}
|
||||
|
||||
//copy instructions if any
|
||||
if (source_len > source_comp_len)
|
||||
{
|
||||
unsigned instr_len = source_len - source_comp_len;
|
||||
hb_memcpy (p, (const char *)c + source_comp_len, instr_len);
|
||||
p += instr_len;
|
||||
}
|
||||
|
||||
unsigned len = p - o;
|
||||
dest_bytes = hb_bytes_t (o, len);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} /* namespace glyf_impl */
|
||||
} /* namespace OT */
|
||||
|
||||
|
||||
#endif /* OT_GLYF_COMPOSITEGLYPH_HH */
|
||||
@@ -0,0 +1,556 @@
|
||||
#ifndef OT_GLYF_GLYPH_HH
|
||||
#define OT_GLYF_GLYPH_HH
|
||||
|
||||
|
||||
#include "../../hb-open-type.hh"
|
||||
|
||||
#include "GlyphHeader.hh"
|
||||
#include "SimpleGlyph.hh"
|
||||
#include "CompositeGlyph.hh"
|
||||
|
||||
|
||||
namespace OT {
|
||||
|
||||
struct glyf_accelerator_t;
|
||||
|
||||
namespace glyf_impl {
|
||||
|
||||
|
||||
enum phantom_point_index_t
|
||||
{
|
||||
PHANTOM_LEFT = 0,
|
||||
PHANTOM_RIGHT = 1,
|
||||
PHANTOM_TOP = 2,
|
||||
PHANTOM_BOTTOM = 3,
|
||||
PHANTOM_COUNT = 4
|
||||
};
|
||||
|
||||
struct Glyph
|
||||
{
|
||||
enum glyph_type_t {
|
||||
EMPTY,
|
||||
SIMPLE,
|
||||
COMPOSITE,
|
||||
};
|
||||
|
||||
public:
|
||||
composite_iter_t get_composite_iterator () const
|
||||
{
|
||||
if (type != COMPOSITE) return composite_iter_t ();
|
||||
return CompositeGlyph (*header, bytes).iter ();
|
||||
}
|
||||
|
||||
const hb_bytes_t trim_padding () const
|
||||
{
|
||||
switch (type) {
|
||||
case COMPOSITE: return CompositeGlyph (*header, bytes).trim_padding ();
|
||||
case SIMPLE: return SimpleGlyph (*header, bytes).trim_padding ();
|
||||
case EMPTY: return bytes;
|
||||
default: return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
void drop_hints ()
|
||||
{
|
||||
switch (type) {
|
||||
case COMPOSITE: CompositeGlyph (*header, bytes).drop_hints (); return;
|
||||
case SIMPLE: SimpleGlyph (*header, bytes).drop_hints (); return;
|
||||
case EMPTY: return;
|
||||
}
|
||||
}
|
||||
|
||||
void set_overlaps_flag ()
|
||||
{
|
||||
switch (type) {
|
||||
case COMPOSITE: CompositeGlyph (*header, bytes).set_overlaps_flag (); return;
|
||||
case SIMPLE: SimpleGlyph (*header, bytes).set_overlaps_flag (); return;
|
||||
case EMPTY: return;
|
||||
}
|
||||
}
|
||||
|
||||
void drop_hints_bytes (hb_bytes_t &dest_start, hb_bytes_t &dest_end) const
|
||||
{
|
||||
switch (type) {
|
||||
case COMPOSITE: CompositeGlyph (*header, bytes).drop_hints_bytes (dest_start); return;
|
||||
case SIMPLE: SimpleGlyph (*header, bytes).drop_hints_bytes (dest_start, dest_end); return;
|
||||
case EMPTY: return;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_composite () const
|
||||
{ return type == COMPOSITE; }
|
||||
|
||||
bool get_all_points_without_var (const hb_face_t *face,
|
||||
contour_point_vector_t &points /* OUT */) const
|
||||
{
|
||||
switch (type) {
|
||||
case SIMPLE:
|
||||
if (unlikely (!SimpleGlyph (*header, bytes).get_contour_points (points)))
|
||||
return false;
|
||||
break;
|
||||
case COMPOSITE:
|
||||
{
|
||||
for (auto &item : get_composite_iterator ())
|
||||
if (unlikely (!item.get_points (points))) return false;
|
||||
break;
|
||||
}
|
||||
case EMPTY:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Init phantom points */
|
||||
if (unlikely (!points.resize (points.length + PHANTOM_COUNT))) return false;
|
||||
hb_array_t<contour_point_t> phantoms = points.as_array ().sub_array (points.length - PHANTOM_COUNT, PHANTOM_COUNT);
|
||||
{
|
||||
int lsb = 0;
|
||||
int h_delta = face->table.hmtx->get_leading_bearing_without_var_unscaled (gid, &lsb) ?
|
||||
(int) header->xMin - lsb : 0;
|
||||
HB_UNUSED int tsb = 0;
|
||||
int v_orig = (int) header->yMax +
|
||||
#ifndef HB_NO_VERTICAL
|
||||
((void) face->table.vmtx->get_leading_bearing_without_var_unscaled (gid, &tsb), tsb)
|
||||
#else
|
||||
0
|
||||
#endif
|
||||
;
|
||||
unsigned h_adv = face->table.hmtx->get_advance_without_var_unscaled (gid);
|
||||
unsigned v_adv =
|
||||
#ifndef HB_NO_VERTICAL
|
||||
face->table.vmtx->get_advance_without_var_unscaled (gid)
|
||||
#else
|
||||
- face->get_upem ()
|
||||
#endif
|
||||
;
|
||||
phantoms[PHANTOM_LEFT].x = h_delta;
|
||||
phantoms[PHANTOM_RIGHT].x = (int) h_adv + h_delta;
|
||||
phantoms[PHANTOM_TOP].y = v_orig;
|
||||
phantoms[PHANTOM_BOTTOM].y = v_orig - (int) v_adv;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void update_mtx (const hb_subset_plan_t *plan,
|
||||
int xMin, int xMax,
|
||||
int yMin, int yMax,
|
||||
const contour_point_vector_t &all_points) const
|
||||
{
|
||||
hb_codepoint_t new_gid = 0;
|
||||
if (!plan->new_gid_for_old_gid (gid, &new_gid))
|
||||
return;
|
||||
|
||||
if (type != EMPTY)
|
||||
{
|
||||
plan->bounds_width_vec[new_gid] = xMax - xMin;
|
||||
plan->bounds_height_vec[new_gid] = yMax - yMin;
|
||||
}
|
||||
|
||||
unsigned len = all_points.length;
|
||||
float leftSideX = all_points[len - 4].x;
|
||||
float rightSideX = all_points[len - 3].x;
|
||||
float topSideY = all_points[len - 2].y;
|
||||
float bottomSideY = all_points[len - 1].y;
|
||||
|
||||
uint32_t hash = hb_hash (new_gid);
|
||||
|
||||
signed hori_aw = roundf (rightSideX - leftSideX);
|
||||
if (hori_aw < 0) hori_aw = 0;
|
||||
int lsb = roundf (xMin - leftSideX);
|
||||
plan->hmtx_map.set_with_hash (new_gid, hash, hb_pair ((unsigned) hori_aw, lsb));
|
||||
//flag value should be computed using non-empty glyphs
|
||||
if (type != EMPTY && lsb != xMin)
|
||||
plan->head_maxp_info.allXMinIsLsb = false;
|
||||
|
||||
signed vert_aw = roundf (topSideY - bottomSideY);
|
||||
if (vert_aw < 0) vert_aw = 0;
|
||||
int tsb = roundf (topSideY - yMax);
|
||||
plan->vmtx_map.set_with_hash (new_gid, hash, hb_pair ((unsigned) vert_aw, tsb));
|
||||
}
|
||||
|
||||
bool compile_header_bytes (const hb_subset_plan_t *plan,
|
||||
const contour_point_vector_t &all_points,
|
||||
hb_bytes_t &dest_bytes /* OUT */) const
|
||||
{
|
||||
GlyphHeader *glyph_header = nullptr;
|
||||
if (!plan->pinned_at_default && type != EMPTY && all_points.length >= 4)
|
||||
{
|
||||
glyph_header = (GlyphHeader *) hb_calloc (1, GlyphHeader::static_size);
|
||||
if (unlikely (!glyph_header)) return false;
|
||||
}
|
||||
|
||||
float xMin = 0, xMax = 0;
|
||||
float yMin = 0, yMax = 0;
|
||||
if (all_points.length > 4)
|
||||
{
|
||||
xMin = xMax = all_points[0].x;
|
||||
yMin = yMax = all_points[0].y;
|
||||
|
||||
unsigned count = all_points.length - 4;
|
||||
for (unsigned i = 1; i < count; i++)
|
||||
{
|
||||
float x = all_points[i].x;
|
||||
float y = all_points[i].y;
|
||||
xMin = hb_min (xMin, x);
|
||||
xMax = hb_max (xMax, x);
|
||||
yMin = hb_min (yMin, y);
|
||||
yMax = hb_max (yMax, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// These are destined for storage in a 16 bit field to clamp the values to
|
||||
// fit into a 16 bit signed integer.
|
||||
int rounded_xMin = hb_clamp (roundf (xMin), -32768.0f, 32767.0f);
|
||||
int rounded_xMax = hb_clamp (roundf (xMax), -32768.0f, 32767.0f);
|
||||
int rounded_yMin = hb_clamp (roundf (yMin), -32768.0f, 32767.0f);
|
||||
int rounded_yMax = hb_clamp (roundf (yMax), -32768.0f, 32767.0f);
|
||||
|
||||
update_mtx (plan, rounded_xMin, rounded_xMax, rounded_yMin, rounded_yMax, all_points);
|
||||
|
||||
if (type != EMPTY)
|
||||
{
|
||||
plan->head_maxp_info.xMin = hb_min (plan->head_maxp_info.xMin, rounded_xMin);
|
||||
plan->head_maxp_info.yMin = hb_min (plan->head_maxp_info.yMin, rounded_yMin);
|
||||
plan->head_maxp_info.xMax = hb_max (plan->head_maxp_info.xMax, rounded_xMax);
|
||||
plan->head_maxp_info.yMax = hb_max (plan->head_maxp_info.yMax, rounded_yMax);
|
||||
}
|
||||
|
||||
/* when pinned at default, no need to compile glyph header
|
||||
* and for empty glyphs: all_points only include phantom points.
|
||||
* just update metrics and then return */
|
||||
if (!glyph_header)
|
||||
return true;
|
||||
|
||||
glyph_header->numberOfContours = header->numberOfContours;
|
||||
|
||||
glyph_header->xMin = rounded_xMin;
|
||||
glyph_header->yMin = rounded_yMin;
|
||||
glyph_header->xMax = rounded_xMax;
|
||||
glyph_header->yMax = rounded_yMax;
|
||||
|
||||
dest_bytes = hb_bytes_t ((const char *)glyph_header, GlyphHeader::static_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool compile_bytes_with_deltas (const hb_subset_plan_t *plan,
|
||||
hb_font_t *font,
|
||||
const glyf_accelerator_t &glyf,
|
||||
hb_bytes_t &dest_start, /* IN/OUT */
|
||||
hb_bytes_t &dest_end /* OUT */)
|
||||
{
|
||||
contour_point_vector_t all_points, points_with_deltas;
|
||||
unsigned composite_contours = 0;
|
||||
head_maxp_info_t *head_maxp_info_p = &plan->head_maxp_info;
|
||||
unsigned *composite_contours_p = &composite_contours;
|
||||
|
||||
// don't compute head/maxp values when glyph has no contours(type is EMPTY)
|
||||
// also ignore .notdef glyph when --notdef-outline is not enabled
|
||||
if (type == EMPTY ||
|
||||
(gid == 0 && !(plan->flags & HB_SUBSET_FLAGS_NOTDEF_OUTLINE)))
|
||||
{
|
||||
head_maxp_info_p = nullptr;
|
||||
composite_contours_p = nullptr;
|
||||
}
|
||||
|
||||
if (!get_points (font, glyf, all_points, &points_with_deltas, head_maxp_info_p, composite_contours_p, false, false))
|
||||
return false;
|
||||
|
||||
// .notdef, set type to empty so we only update metrics and don't compile bytes for
|
||||
// it
|
||||
if (gid == 0 &&
|
||||
!(plan->flags & HB_SUBSET_FLAGS_NOTDEF_OUTLINE))
|
||||
{
|
||||
type = EMPTY;
|
||||
dest_start = hb_bytes_t ();
|
||||
dest_end = hb_bytes_t ();
|
||||
}
|
||||
|
||||
//dont compile bytes when pinned at default, just recalculate bounds
|
||||
if (!plan->pinned_at_default)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case COMPOSITE:
|
||||
if (!CompositeGlyph (*header, bytes).compile_bytes_with_deltas (dest_start,
|
||||
points_with_deltas,
|
||||
dest_end))
|
||||
return false;
|
||||
break;
|
||||
case SIMPLE:
|
||||
if (!SimpleGlyph (*header, bytes).compile_bytes_with_deltas (all_points,
|
||||
plan->flags & HB_SUBSET_FLAGS_NO_HINTING,
|
||||
dest_end))
|
||||
return false;
|
||||
break;
|
||||
case EMPTY:
|
||||
/* set empty bytes for empty glyph
|
||||
* do not use source glyph's pointers */
|
||||
dest_start = hb_bytes_t ();
|
||||
dest_end = hb_bytes_t ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!compile_header_bytes (plan, all_points, dest_start))
|
||||
{
|
||||
dest_end.fini ();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* Note: Recursively calls itself.
|
||||
* all_points includes phantom points
|
||||
*/
|
||||
template <typename accelerator_t>
|
||||
bool get_points (hb_font_t *font, const accelerator_t &glyf_accelerator,
|
||||
contour_point_vector_t &all_points /* OUT */,
|
||||
contour_point_vector_t *points_with_deltas = nullptr, /* OUT */
|
||||
head_maxp_info_t * head_maxp_info = nullptr, /* OUT */
|
||||
unsigned *composite_contours = nullptr, /* OUT */
|
||||
bool shift_points_hori = true,
|
||||
bool use_my_metrics = true,
|
||||
bool phantom_only = false,
|
||||
hb_array_t<const int> coords = hb_array_t<const int> (),
|
||||
hb_map_t *current_glyphs = nullptr,
|
||||
unsigned int depth = 0,
|
||||
unsigned *edge_count = nullptr) const
|
||||
{
|
||||
if (unlikely (depth > HB_MAX_NESTING_LEVEL)) return false;
|
||||
unsigned stack_edge_count = 0;
|
||||
if (!edge_count) edge_count = &stack_edge_count;
|
||||
if (unlikely (*edge_count > HB_MAX_GRAPH_EDGE_COUNT)) return false;
|
||||
(*edge_count)++;
|
||||
|
||||
hb_map_t current_glyphs_stack;
|
||||
if (current_glyphs == nullptr)
|
||||
current_glyphs = ¤t_glyphs_stack;
|
||||
|
||||
if (head_maxp_info)
|
||||
{
|
||||
head_maxp_info->maxComponentDepth = hb_max (head_maxp_info->maxComponentDepth, depth);
|
||||
}
|
||||
|
||||
if (!coords)
|
||||
coords = hb_array (font->coords, font->num_coords);
|
||||
|
||||
contour_point_vector_t stack_points;
|
||||
contour_point_vector_t &points = type == SIMPLE ? all_points : stack_points;
|
||||
unsigned old_length = points.length;
|
||||
|
||||
switch (type) {
|
||||
case SIMPLE:
|
||||
if (depth == 0 && head_maxp_info)
|
||||
head_maxp_info->maxContours = hb_max (head_maxp_info->maxContours, (unsigned) header->numberOfContours);
|
||||
if (depth > 0 && composite_contours)
|
||||
*composite_contours += (unsigned) header->numberOfContours;
|
||||
if (unlikely (!SimpleGlyph (*header, bytes).get_contour_points (all_points, phantom_only)))
|
||||
return false;
|
||||
break;
|
||||
case COMPOSITE:
|
||||
{
|
||||
for (auto &item : get_composite_iterator ())
|
||||
if (unlikely (!item.get_points (points))) return false;
|
||||
break;
|
||||
}
|
||||
case EMPTY:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Init phantom points */
|
||||
if (unlikely (!points.resize (points.length + PHANTOM_COUNT))) return false;
|
||||
hb_array_t<contour_point_t> phantoms = points.as_array ().sub_array (points.length - PHANTOM_COUNT, PHANTOM_COUNT);
|
||||
{
|
||||
int lsb = 0;
|
||||
int h_delta = glyf_accelerator.hmtx->get_leading_bearing_without_var_unscaled (gid, &lsb) ?
|
||||
(int) header->xMin - lsb : 0;
|
||||
HB_UNUSED int tsb = 0;
|
||||
int v_orig = (int) header->yMax +
|
||||
#ifndef HB_NO_VERTICAL
|
||||
((void) glyf_accelerator.vmtx->get_leading_bearing_without_var_unscaled (gid, &tsb), tsb)
|
||||
#else
|
||||
0
|
||||
#endif
|
||||
;
|
||||
unsigned h_adv = glyf_accelerator.hmtx->get_advance_without_var_unscaled (gid);
|
||||
unsigned v_adv =
|
||||
#ifndef HB_NO_VERTICAL
|
||||
glyf_accelerator.vmtx->get_advance_without_var_unscaled (gid)
|
||||
#else
|
||||
- font->face->get_upem ()
|
||||
#endif
|
||||
;
|
||||
phantoms[PHANTOM_LEFT].x = h_delta;
|
||||
phantoms[PHANTOM_RIGHT].x = (int) h_adv + h_delta;
|
||||
phantoms[PHANTOM_TOP].y = v_orig;
|
||||
phantoms[PHANTOM_BOTTOM].y = v_orig - (int) v_adv;
|
||||
}
|
||||
|
||||
#ifndef HB_NO_VAR
|
||||
if (coords)
|
||||
glyf_accelerator.gvar->apply_deltas_to_points (gid,
|
||||
coords,
|
||||
points.as_array ().sub_array (old_length),
|
||||
phantom_only && type == SIMPLE);
|
||||
#endif
|
||||
|
||||
// mainly used by CompositeGlyph calculating new X/Y offset value so no need to extend it
|
||||
// with child glyphs' points
|
||||
if (points_with_deltas != nullptr && depth == 0 && type == COMPOSITE)
|
||||
{
|
||||
if (unlikely (!points_with_deltas->resize (points.length))) return false;
|
||||
*points_with_deltas = points;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case SIMPLE:
|
||||
if (depth == 0 && head_maxp_info)
|
||||
head_maxp_info->maxPoints = hb_max (head_maxp_info->maxPoints, all_points.length - old_length - 4);
|
||||
break;
|
||||
case COMPOSITE:
|
||||
{
|
||||
unsigned int comp_index = 0;
|
||||
for (auto &item : get_composite_iterator ())
|
||||
{
|
||||
hb_codepoint_t item_gid = item.get_gid ();
|
||||
|
||||
if (unlikely (current_glyphs->has (item_gid)))
|
||||
continue;
|
||||
|
||||
current_glyphs->add (item_gid);
|
||||
|
||||
unsigned old_count = all_points.length;
|
||||
|
||||
if (unlikely ((!phantom_only || (use_my_metrics && item.is_use_my_metrics ())) &&
|
||||
!glyf_accelerator.glyph_for_gid (item_gid)
|
||||
.get_points (font,
|
||||
glyf_accelerator,
|
||||
all_points,
|
||||
points_with_deltas,
|
||||
head_maxp_info,
|
||||
composite_contours,
|
||||
shift_points_hori,
|
||||
use_my_metrics,
|
||||
phantom_only,
|
||||
coords,
|
||||
current_glyphs,
|
||||
depth + 1,
|
||||
edge_count)))
|
||||
{
|
||||
current_glyphs->del (item_gid);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto comp_points = all_points.as_array ().sub_array (old_count);
|
||||
|
||||
/* Copy phantom points from component if USE_MY_METRICS flag set */
|
||||
if (use_my_metrics && item.is_use_my_metrics ())
|
||||
for (unsigned int i = 0; i < PHANTOM_COUNT; i++)
|
||||
phantoms[i] = comp_points[comp_points.length - PHANTOM_COUNT + i];
|
||||
|
||||
if (comp_points) // Empty in case of phantom_only
|
||||
{
|
||||
float matrix[4];
|
||||
contour_point_t default_trans;
|
||||
item.get_transformation (matrix, default_trans);
|
||||
|
||||
/* Apply component transformation & translation (with deltas applied) */
|
||||
item.transform_points (comp_points, matrix, points[comp_index]);
|
||||
}
|
||||
|
||||
if (item.is_anchored () && !phantom_only)
|
||||
{
|
||||
unsigned int p1, p2;
|
||||
item.get_anchor_points (p1, p2);
|
||||
if (likely (p1 < all_points.length && p2 < comp_points.length))
|
||||
{
|
||||
contour_point_t delta;
|
||||
delta.init (all_points[p1].x - comp_points[p2].x,
|
||||
all_points[p1].y - comp_points[p2].y);
|
||||
|
||||
item.translate (delta, comp_points);
|
||||
}
|
||||
}
|
||||
|
||||
all_points.resize (all_points.length - PHANTOM_COUNT);
|
||||
|
||||
if (all_points.length > HB_GLYF_MAX_POINTS)
|
||||
{
|
||||
current_glyphs->del (item_gid);
|
||||
return false;
|
||||
}
|
||||
|
||||
comp_index++;
|
||||
current_glyphs->del (item_gid);
|
||||
}
|
||||
|
||||
if (head_maxp_info && depth == 0)
|
||||
{
|
||||
if (composite_contours)
|
||||
head_maxp_info->maxCompositeContours = hb_max (head_maxp_info->maxCompositeContours, *composite_contours);
|
||||
head_maxp_info->maxCompositePoints = hb_max (head_maxp_info->maxCompositePoints, all_points.length);
|
||||
head_maxp_info->maxComponentElements = hb_max (head_maxp_info->maxComponentElements, comp_index);
|
||||
}
|
||||
all_points.extend (phantoms);
|
||||
} break;
|
||||
case EMPTY:
|
||||
all_points.extend (phantoms);
|
||||
break;
|
||||
}
|
||||
|
||||
if (depth == 0 && shift_points_hori) /* Apply at top level */
|
||||
{
|
||||
/* Undocumented rasterizer behavior:
|
||||
* Shift points horizontally by the updated left side bearing
|
||||
*/
|
||||
float v = -phantoms[PHANTOM_LEFT].x;
|
||||
if (v)
|
||||
for (auto &point : all_points)
|
||||
point.x += v;
|
||||
}
|
||||
|
||||
return !all_points.in_error ();
|
||||
}
|
||||
|
||||
bool get_extents_without_var_scaled (hb_font_t *font, const glyf_accelerator_t &glyf_accelerator,
|
||||
hb_glyph_extents_t *extents) const
|
||||
{
|
||||
if (type == EMPTY) return true; /* Empty glyph; zero extents. */
|
||||
return header->get_extents_without_var_scaled (font, glyf_accelerator, gid, extents);
|
||||
}
|
||||
|
||||
hb_bytes_t get_bytes () const { return bytes; }
|
||||
glyph_type_t get_type () const { return type; }
|
||||
const GlyphHeader *get_header () const { return header; }
|
||||
|
||||
Glyph () : bytes (),
|
||||
header (bytes.as<GlyphHeader> ()),
|
||||
gid (-1),
|
||||
type(EMPTY)
|
||||
{}
|
||||
|
||||
Glyph (hb_bytes_t bytes_,
|
||||
hb_codepoint_t gid_ = (unsigned) -1) : bytes (bytes_),
|
||||
header (bytes.as<GlyphHeader> ()),
|
||||
gid (gid_)
|
||||
{
|
||||
int num_contours = header->numberOfContours;
|
||||
if (unlikely (num_contours == 0)) type = EMPTY;
|
||||
else if (num_contours > 0) type = SIMPLE;
|
||||
else if (num_contours <= -1) type = COMPOSITE;
|
||||
else type = EMPTY; // Spec deviation; Spec says COMPOSITE, but not seen in the wild.
|
||||
}
|
||||
|
||||
protected:
|
||||
hb_bytes_t bytes;
|
||||
const GlyphHeader *header;
|
||||
hb_codepoint_t gid;
|
||||
glyph_type_t type;
|
||||
};
|
||||
|
||||
|
||||
} /* namespace glyf_impl */
|
||||
} /* namespace OT */
|
||||
|
||||
|
||||
#endif /* OT_GLYF_GLYPH_HH */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user