blob: ed6a4e03292b11372f80c9aede7350e47b7def00 [file] [log] [blame]
/*
* Copyright 2024 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkCanvas.h"
#include "include/core/SkContourMeasure.h"
#include "include/core/SkFont.h"
#include "include/core/SkFontMetrics.h"
#include "include/core/SkFontMgr.h"
#include "include/core/SkPath.h"
#include "include/core/SkRRect.h"
#include "include/core/SkTypeface.h"
#include "include/utils/SkNoDrawCanvas.h"
#include "src/base/SkRandom.h"
#include "tools/SkMetaData.h"
#include "tools/ToolUtils.h"
#include "tools/fonts/FontToolUtils.h"
#include "tools/timer/TimeUtils.h"
#include "tools/viewer/Slide.h"
namespace {
class TypefaceSlide : public Slide {
public:
TypefaceSlide() { fName = "Typeface Viewer"; }
void load(SkScalar w, SkScalar h) override {
fWindowSize = {w, h};
fBaseTypeface = ToolUtils::CreateTypefaceFromResource("fonts/Variable.ttf");
fCurrentTypeface = fBaseTypeface;
fVariationSliders = ToolUtils::VariationSliders(fCurrentTypeface.get(), fVariationPosition);
fPathDirectionIndicator.reset();
fPathDirectionIndicator.moveTo(0, -3);
fPathDirectionIndicator.lineTo(3, 0);
fPathDirectionIndicator.lineTo(0, 3);
fPathDirectionIndicator.close();
fPathDirectionIndicatorPaint.setColor(SK_ColorRED);
fPathDirectionIndicatorPaint.setStroke(true);
fPathDirectionIndicatorPaint.setStrokeWidth(0);
}
void resize(SkScalar w, SkScalar h) override { fWindowSize = {w, h}; }
void unload() override {
fBaseTypeface = nullptr;
fCurrentTypeface = nullptr;
}
SkISize getDimensions() const override {
if (fDrawArea.isEmpty()) {
TypefaceSlide& self = *const_cast<TypefaceSlide*>(this);
SkNoDrawCanvas noDraw(fWindowSize.width(), fWindowSize.height());
self.draw(&noDraw);
}
return fDrawArea;
}
void updateCurrentTypeface() {
if (!fBaseTypeface) {
return;
}
SkSpan<const SkFontArguments::VariationPosition::Coordinate> coords =
fVariationSliders.getCoordinates();
SkFontArguments::VariationPosition varPos = {coords.data(),
static_cast<int>(coords.size())};
SkFontArguments args;
args.setVariationDesignPosition(varPos);
fCurrentTypeface = fBaseTypeface->makeClone(args);
fCurrentTypefaceDirty = false;
}
void draw(SkCanvas* canvas) override {
SkPaint paint;
if (fOutline) {
paint.setStroke(true);
paint.setStrokeWidth(0);
}
if (fCurrentTypefaceDirty) {
this->updateCurrentTypeface();
}
SkFont font(fCurrentTypeface, fFontSize);
SkFontMetrics metrics;
font.getMetrics(&metrics);
SkPoint origin{0, 0};
SkPoint position{0, 0};
SkRect drawBounds = SkRect::MakeXYWH(0, 0, 1, 1);
struct Line {
SkRect bounds;
SkGlyphID firstGlyph = 0; // inclusive
SkGlyphID lastGlyph = 0; // inclusive
int number = 0;
} line;
int numGlyphs = fCurrentTypeface->countGlyphs();
if (numGlyphs == 0) {
fDrawArea.setEmpty();
return;
}
SkGlyphID lastGlyph = numGlyphs - 1;
for (SkGlyphID glyph = 0; true; ++glyph) {
// measure a line
SkRect beginLineGlyphBounds;
{
SkRect newLineBounds = line.bounds;
if (glyph != line.firstGlyph) {
newLineBounds.fRight += 10;
}
SkScalar advance;
SkRect glyphBounds;
font.getWidths(&glyph, 1, &advance, &glyphBounds);
SkRect advanceBounds = SkRect::MakeWH(advance, 1);
SkRect glyphAndAdvanceBounds = glyphBounds;
glyphAndAdvanceBounds.join(advanceBounds);
beginLineGlyphBounds = glyphAndAdvanceBounds;
beginLineGlyphBounds.offset(-beginLineGlyphBounds.fLeft, 0);
SkRect glyphDrawBounds = beginLineGlyphBounds.makeOffset(newLineBounds.right(), 0);
if (line.number == -1) {
SkPaint p;
p.setColor(SK_ColorBLUE);
canvas->drawRect(glyphDrawBounds, p);
}
newLineBounds.join(glyphDrawBounds);
if (newLineBounds.width() < fWindowSize.width()) {
line.lastGlyph = glyph;
line.bounds = newLineBounds;
if (glyph != lastGlyph) {
continue;
}
}
}
// draw the line
position.fY -= line.bounds.top();
for (SkGlyphID gid = line.firstGlyph; gid <= line.lastGlyph; ++gid) {
if (gid != line.firstGlyph) {
position.fX += 10;
}
SkScalar advance;
SkRect glyphBounds;
font.getWidths(&gid, 1, &advance, &glyphBounds);
SkRect advanceBounds = SkRect::MakeWH(advance, 1);
SkRect glyphAndAdvanceBounds = glyphBounds;
glyphAndAdvanceBounds.join(advanceBounds);
position.fX -= glyphAndAdvanceBounds.left();
if (fDrawGlyphMetrics) {
SkPaint metricPaint;
metricPaint.setColor(SK_ColorRED);
canvas->drawRect(glyphBounds.makeOffset(position), metricPaint);
metricPaint.setColor(SK_ColorGREEN);
canvas->drawRect(advanceBounds.makeOffset(position), metricPaint);
}
canvas->drawGlyphs(1, &gid, &position, origin, font, paint);
// TODO: also handle drawable by using a paint override canvas?
SkPath glyphPath;
if (fOutline && font.getPath(gid, &glyphPath)) {
SkContourMeasureIter iter(glyphPath, false);
sk_sp<SkContourMeasure> contour;
while ((contour = iter.next())) {
SkPoint contourStart;
SkVector tangent;
if (contour->getPosTan(0, &contourStart, &tangent)) {
contourStart += position;
SkAutoCanvasRestore acr(canvas, true);
SkMatrix matrix;
matrix.setSinCos(tangent.y(), tangent.x(), 0, 0);
matrix.postTranslate(contourStart.x(), contourStart.y());
canvas->concat(matrix);
canvas->drawPath(fPathDirectionIndicator, fPathDirectionIndicatorPaint);
}
}
}
position.fX += glyphAndAdvanceBounds.right();
}
if (line.lastGlyph == lastGlyph) {
break;
}
drawBounds.join(line.bounds.makeOffset(-line.bounds.fLeft, position.fY));
position.fX = 0;
position.fY += line.bounds.bottom() + 10;
line.bounds = beginLineGlyphBounds;
line.firstGlyph = glyph;
line.lastGlyph = glyph;
++line.number;
}
fDrawArea = drawBounds.roundOut().size();
}
bool onGetControls(SkMetaData* controls) override {
// requested font size
SkScalar size[3] = {fFontSize, 0, 256};
controls->setScalars("Size", 3, size);
// TODO: toggle glyph numbers on top?
// TODO: limit number of glyphs and set first glyph
// TODO: choose typeface factory
// TODO: choose between typefaces
// TODO: font metrics like underline, strikeout, x-height, cap-height, etc.
// show glyph metrics like advances and bounds
controls->setBool("Glyph Metrics", fDrawGlyphMetrics);
// hairline contours with initial direction mark
controls->setBool("Outline", fOutline);
return fVariationSliders.writeControls(controls);
}
void onSetControls(const SkMetaData& controls) override {
SkScalar size[3] = {0};
int numReturnedScalars = 0;
SkASSERT_RELEASE(controls.findScalars("Size", &numReturnedScalars, size));
SkASSERT_RELEASE(numReturnedScalars == 3);
if (fFontSize != size[0]) {
fFontSize = size[0];
fDrawArea.setEmpty();
}
controls.findBool("Glyph Metrics", &fDrawGlyphMetrics);
controls.findBool("Outline", &fOutline);
fVariationSliders.readControls(controls, &fCurrentTypefaceDirty);
}
private:
sk_sp<SkTypeface> fBaseTypeface;
sk_sp<SkTypeface> fCurrentTypeface;
std::unique_ptr<SkFontArguments::VariationPosition::Coordinate[]> fCoordinates;
SkFontArguments::VariationPosition fVariationPosition;
ToolUtils::VariationSliders fVariationSliders;
bool fCurrentTypefaceDirty = true;
SkScalar fFontSize = 12;
bool fOutline = false;
bool fDrawGlyphMetrics = false;
SkSize fWindowSize;
SkISize fDrawArea;
SkPath fPathDirectionIndicator;
SkPaint fPathDirectionIndicatorPaint;
};
} //namespace
//////////////////////////////////////////////////////////////////////////////
DEF_SLIDE( return new TypefaceSlide(); )