/* * Copyright (c) 2020, Srimanta Barua * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include namespace TTF { extern u16 be_u16(const u8* ptr); extern u32 be_u32(const u8* ptr); extern i16 be_i16(const u8* ptr); extern float be_fword(const u8* ptr); enum class SimpleGlyfFlags { // From spec. OnCurve = 0x01, XShortVector = 0x02, YShortVector = 0x04, RepeatFlag = 0x08, XIsSameOrPositiveXShortVector = 0x10, YIsSameOrPositiveYShortVector = 0x20, // Combinations XMask = 0x12, YMask = 0x24, XLongVector = 0x00, YLongVector = 0x00, XNegativeShortVector = 0x02, YNegativeShortVector = 0x04, XPositiveShortVector = 0x12, YPositiveShortVector = 0x24, }; enum class CompositeGlyfFlags { Arg1AndArg2AreWords = 0x0001, ArgsAreXYValues = 0x0002, RoundXYToGrid = 0x0004, WeHaveAScale = 0x0008, MoreComponents = 0x0020, WeHaveAnXAndYScale = 0x0040, WeHaveATwoByTwo = 0x0080, WeHaveInstructions = 0x0100, UseMyMetrics = 0x0200, OverlapCompound = 0x0400, // Not relevant - can overlap without this set ScaledComponentOffset = 0x0800, UnscaledComponentOffset = 0x1000, }; class PointIterator { public: struct Item { bool on_curve; Gfx::FloatPoint point; }; PointIterator(const ReadonlyBytes& slice, u16 num_points, u32 flags_offset, u32 x_offset, u32 y_offset, Gfx::AffineTransform affine) : m_slice(slice) , m_points_remaining(num_points) , m_flags_offset(flags_offset) , m_x_offset(x_offset) , m_y_offset(y_offset) , m_affine(affine) { } Optional next() { if (m_points_remaining == 0) { return {}; } if (m_flags_remaining > 0) { m_flags_remaining--; } else { m_flag = m_slice[m_flags_offset++]; if (m_flag & (u8)SimpleGlyfFlags::RepeatFlag) { m_flags_remaining = m_slice[m_flags_offset++]; } } switch (m_flag & (u8)SimpleGlyfFlags::XMask) { case (u8)SimpleGlyfFlags::XLongVector: m_last_point.set_x(m_last_point.x() + be_i16(m_slice.offset_pointer(m_x_offset))); m_x_offset += 2; break; case (u8)SimpleGlyfFlags::XNegativeShortVector: m_last_point.set_x(m_last_point.x() - m_slice[m_x_offset++]); break; case (u8)SimpleGlyfFlags::XPositiveShortVector: m_last_point.set_x(m_last_point.x() + m_slice[m_x_offset++]); break; default: break; } switch (m_flag & (u8)SimpleGlyfFlags::YMask) { case (u8)SimpleGlyfFlags::YLongVector: m_last_point.set_y(m_last_point.y() + be_i16(m_slice.offset_pointer(m_y_offset))); m_y_offset += 2; break; case (u8)SimpleGlyfFlags::YNegativeShortVector: m_last_point.set_y(m_last_point.y() - m_slice[m_y_offset++]); break; case (u8)SimpleGlyfFlags::YPositiveShortVector: m_last_point.set_y(m_last_point.y() + m_slice[m_y_offset++]); break; default: break; } m_points_remaining--; Item ret = { .on_curve = (m_flag & (u8)SimpleGlyfFlags::OnCurve) != 0, .point = m_affine.map(m_last_point), }; return ret; } private: ReadonlyBytes m_slice; u16 m_points_remaining; u8 m_flag { 0 }; Gfx::FloatPoint m_last_point = { 0.0f, 0.0f }; u32 m_flags_remaining = { 0 }; u32 m_flags_offset; u32 m_x_offset; u32 m_y_offset; Gfx::AffineTransform m_affine; }; Optional Glyf::Glyph::ComponentIterator::next() { if (!m_has_more) { return {}; } u16 flags = be_u16(m_slice.offset_pointer(m_offset)); m_offset += 2; u16 glyph_id = be_u16(m_slice.offset_pointer(m_offset)); m_offset += 2; i16 arg1 = 0, arg2 = 0; if (flags & (u16)CompositeGlyfFlags::Arg1AndArg2AreWords) { arg1 = be_i16(m_slice.offset_pointer(m_offset)); m_offset += 2; arg2 = be_i16(m_slice.offset_pointer(m_offset)); m_offset += 2; } else { arg1 = (i8)m_slice[m_offset++]; arg2 = (i8)m_slice[m_offset++]; } float a = 1.0, b = 0.0, c = 0.0, d = 1.0, e = 0.0, f = 0.0; if (flags & (u16)CompositeGlyfFlags::WeHaveATwoByTwo) { a = be_fword(m_slice.offset_pointer(m_offset)); m_offset += 2; b = be_fword(m_slice.offset_pointer(m_offset)); m_offset += 2; c = be_fword(m_slice.offset_pointer(m_offset)); m_offset += 2; d = be_fword(m_slice.offset_pointer(m_offset)); m_offset += 2; } else if (flags & (u16)CompositeGlyfFlags::WeHaveAnXAndYScale) { a = be_fword(m_slice.offset_pointer(m_offset)); m_offset += 2; d = be_fword(m_slice.offset_pointer(m_offset)); m_offset += 2; } else if (flags & (u16)CompositeGlyfFlags::WeHaveAScale) { a = be_fword(m_slice.offset_pointer(m_offset)); m_offset += 2; d = a; } // FIXME: Handle UseMyMetrics, ScaledComponentOffset, UnscaledComponentOffset, non-ArgsAreXYValues if (flags & (u16)CompositeGlyfFlags::ArgsAreXYValues) { e = arg1; f = arg2; } else { TODO(); } if (flags & (u16)CompositeGlyfFlags::UseMyMetrics) { TODO(); } if (flags & (u16)CompositeGlyfFlags::ScaledComponentOffset) { TODO(); } if (flags & (u16)CompositeGlyfFlags::UnscaledComponentOffset) { TODO(); } m_has_more = (flags & (u16)CompositeGlyfFlags::MoreComponents); return Item { .glyph_id = glyph_id, .affine = Gfx::AffineTransform(a, b, c, d, e, f), }; } Rasterizer::Rasterizer(Gfx::IntSize size) : m_size(size) { m_data.resize(m_size.width() * m_size.height()); for (int i = 0; i < m_size.width() * m_size.height(); i++) { m_data[i] = 0.0; } } void Rasterizer::draw_path(Gfx::Path& path) { for (auto& line : path.split_lines()) { draw_line(line.from, line.to); } } RefPtr Rasterizer::accumulate() { auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, m_size); Color base_color = Color::from_rgb(0xffffff); for (int y = 0; y < m_size.height(); y++) { float accumulator = 0.0; for (int x = 0; x < m_size.width(); x++) { accumulator += m_data[y * m_size.width() + x]; float value = accumulator; if (value < 0.0) { value = -value; } if (value > 1.0) { value = 1.0; } u8 alpha = value * 255.0; bitmap->set_pixel(x, y, base_color.with_alpha(alpha)); } } return bitmap; } void Rasterizer::draw_line(Gfx::FloatPoint p0, Gfx::FloatPoint p1) { // FIXME: Shift x and y according to dy/dx if (p0.x() < 0.0) { p0.set_x(roundf(p0.x())); } if (p0.y() < 0.0) { p0.set_y(roundf(p0.y())); } if (p1.x() < 0.0) { p1.set_x(roundf(p1.x())); } if (p1.y() < 0.0) { p1.set_y(roundf(p1.y())); } if (!(p0.x() >= 0.0 && p0.y() >= 0.0 && p0.x() <= m_size.width() && p0.y() <= m_size.height())) { dbgln("!P0({},{})", p0.x(), p0.y()); return; } if (!(p1.x() >= 0.0 && p1.y() >= 0.0 && p1.x() <= m_size.width() && p1.y() <= m_size.height())) { dbgln("!P1({},{})", p1.x(), p1.y()); return; } ASSERT(p0.x() >= 0.0 && p0.y() >= 0.0 && p0.x() <= m_size.width() && p0.y() <= m_size.height()); ASSERT(p1.x() >= 0.0 && p1.y() >= 0.0 && p1.x() <= m_size.width() && p1.y() <= m_size.height()); // If we're on the same Y, there's no need to draw if (p0.y() == p1.y()) { return; } float direction = -1.0; if (p1.y() < p0.y()) { direction = 1.0; auto tmp = p0; p0 = p1; p1 = tmp; } float dxdy = (p1.x() - p0.x()) / (p1.y() - p0.y()); u32 y0 = floor(p0.y()); u32 y1 = ceil(p1.y()); float x_cur = p0.x(); for (u32 y = y0; y < y1; y++) { u32 line_offset = m_size.width() * y; float dy = min(y + 1.0f, p1.y()) - max((float)y, p0.y()); float directed_dy = dy * direction; float x_next = x_cur + dy * dxdy; if (x_next < 0.0) { x_next = 0.0; } float x0 = x_cur; float x1 = x_next; if (x1 < x0) { x1 = x_cur; x0 = x_next; } float x0_floor = floor(x0); float x1_ceil = ceil(x1); u32 x0i = x0_floor; if (x1_ceil <= x0_floor + 1.0) { // If x0 and x1 are within the same pixel, then area to the right is (1 - (mid(x0, x1) - x0_floor)) * dy float area = ((x0 + x1) * 0.5) - x0_floor; m_data[line_offset + x0i] += directed_dy * (1.0 - area); m_data[line_offset + x0i + 1] += directed_dy * area; } else { float dydx = 1.0 / dxdy; float x0_right = 1.0 - (x0 - x0_floor); u32 x1_floor_i = floor(x1); float area_upto_here = 0.5 * x0_right * x0_right * dydx; m_data[line_offset + x0i] += direction * area_upto_here; for (u32 x = x0i + 1; x < x1_floor_i; x++) { x0_right += 1.0; float total_area_here = 0.5 * x0_right * x0_right * dydx; m_data[line_offset + x] += direction * (total_area_here - area_upto_here); area_upto_here = total_area_here; } m_data[line_offset + x1_floor_i] += direction * (dy - area_upto_here); } x_cur = x_next; } } Optional Loca::from_slice(const ReadonlyBytes& slice, u32 num_glyphs, IndexToLocFormat index_to_loc_format) { switch (index_to_loc_format) { case IndexToLocFormat::Offset16: if (slice.size() < num_glyphs * 2) { return {}; } break; case IndexToLocFormat::Offset32: if (slice.size() < num_glyphs * 4) { return {}; } break; } return Loca(slice, num_glyphs, index_to_loc_format); } u32 Loca::get_glyph_offset(u32 glyph_id) const { ASSERT(glyph_id < m_num_glyphs); switch (m_index_to_loc_format) { case IndexToLocFormat::Offset16: return ((u32)be_u16(m_slice.offset_pointer(glyph_id * 2))) * 2; case IndexToLocFormat::Offset32: return be_u32(m_slice.offset_pointer(glyph_id * 4)); default: ASSERT_NOT_REACHED(); } } static void get_ttglyph_offsets(const ReadonlyBytes& slice, u32 num_points, u32 flags_offset, u32* x_offset, u32* y_offset) { u32 flags_size = 0; u32 x_size = 0; u32 repeat_count; while (num_points > 0) { u8 flag = slice[flags_offset + flags_size]; if (flag & (u8)SimpleGlyfFlags::RepeatFlag) { flags_size++; repeat_count = slice[flags_offset + flags_size] + 1; } else { repeat_count = 1; } flags_size++; switch (flag & (u8)SimpleGlyfFlags::XMask) { case (u8)SimpleGlyfFlags::XLongVector: x_size += repeat_count * 2; break; case (u8)SimpleGlyfFlags::XNegativeShortVector: case (u8)SimpleGlyfFlags::XPositiveShortVector: x_size += repeat_count; break; default: break; } num_points -= repeat_count; } *x_offset = flags_offset + flags_size; *y_offset = *x_offset + x_size; } void Glyf::Glyph::raster_inner(Rasterizer& rasterizer, Gfx::AffineTransform& affine) const { // Get offset for flags, x, and y. u16 num_points = be_u16(m_slice.offset_pointer((m_num_contours - 1) * 2)) + 1; u16 num_instructions = be_u16(m_slice.offset_pointer(m_num_contours * 2)); u32 flags_offset = m_num_contours * 2 + 2 + num_instructions; u32 x_offset = 0; u32 y_offset = 0; get_ttglyph_offsets(m_slice, num_points, flags_offset, &x_offset, &y_offset); // Prepare to render glyph. Gfx::Path path; PointIterator point_iterator(m_slice, num_points, flags_offset, x_offset, y_offset, affine); int last_contour_end = -1; i32 contour_index = 0; u32 contour_size = 0; Optional contour_start = {}; Optional last_offcurve_point = {}; // Render glyph while (true) { if (!contour_start.has_value()) { if (contour_index >= m_num_contours) { break; } int current_contour_end = be_u16(m_slice.offset_pointer(contour_index++ * 2)); contour_size = current_contour_end - last_contour_end; last_contour_end = current_contour_end; auto opt_item = point_iterator.next(); ASSERT(opt_item.has_value()); contour_start = opt_item.value().point; path.move_to(contour_start.value()); contour_size--; } else if (!last_offcurve_point.has_value()) { if (contour_size > 0) { auto opt_item = point_iterator.next(); // FIXME: Should we draw a line to the first point here? if (!opt_item.has_value()) { break; } auto item = opt_item.value(); contour_size--; if (item.on_curve) { path.line_to(item.point); } else if (contour_size > 0) { auto opt_next_item = point_iterator.next(); // FIXME: Should we draw a quadratic bezier to the first point here? if (!opt_next_item.has_value()) { break; } auto next_item = opt_next_item.value(); contour_size--; if (next_item.on_curve) { path.quadratic_bezier_curve_to(item.point, next_item.point); } else { auto mid_point = (item.point + next_item.point) * 0.5f; path.quadratic_bezier_curve_to(item.point, mid_point); last_offcurve_point = next_item.point; } } else { path.quadratic_bezier_curve_to(item.point, contour_start.value()); contour_start = {}; } } else { path.line_to(contour_start.value()); contour_start = {}; } } else { auto point0 = last_offcurve_point.value(); last_offcurve_point = {}; if (contour_size > 0) { auto opt_item = point_iterator.next(); // FIXME: Should we draw a quadratic bezier to the first point here? if (!opt_item.has_value()) { break; } auto item = opt_item.value(); contour_size--; if (item.on_curve) { path.quadratic_bezier_curve_to(point0, item.point); } else { auto mid_point = (point0 + item.point) * 0.5f; path.quadratic_bezier_curve_to(point0, mid_point); last_offcurve_point = item.point; } } else { path.quadratic_bezier_curve_to(point0, contour_start.value()); contour_start = {}; } } } rasterizer.draw_path(path); } RefPtr Glyf::Glyph::raster_simple(float x_scale, float y_scale) const { u32 width = (u32)(ceil((m_xmax - m_xmin) * x_scale)) + 2; u32 height = (u32)(ceil((m_ymax - m_ymin) * y_scale)) + 2; Rasterizer rasterizer(Gfx::IntSize(width, height)); auto affine = Gfx::AffineTransform().scale(x_scale, -y_scale).translate(-m_xmin, -m_ymax); raster_inner(rasterizer, affine); return rasterizer.accumulate(); } Glyf::Glyph Glyf::glyph(u32 offset) const { ASSERT(m_slice.size() >= offset + (u32)Sizes::GlyphHeader); i16 num_contours = be_i16(m_slice.offset_pointer(offset)); i16 xmin = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::XMin)); i16 ymin = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::YMin)); i16 xmax = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::XMax)); i16 ymax = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::YMax)); auto slice = ReadonlyBytes(m_slice.offset_pointer(offset + (u32)Sizes::GlyphHeader), m_slice.size() - offset - (u32)Sizes::GlyphHeader); return Glyph(slice, xmin, ymin, xmax, ymax, num_contours); } }