Skip to content

Commit

Permalink
fix: Consider font size difference between macOS and iOS. iOS image r…
Browse files Browse the repository at this point in the history
…esolution is fit with device.
  • Loading branch information
fummicc1 committed Oct 19, 2024
1 parent ae70a9f commit 7849196
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 84 deletions.
Binary file modified Fixtures/outputs/category.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions Sources/Csv2ImgCore/ImageMaker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,13 @@ final class ImageMaker: ImageMakerType {
throw ImageMakingError.noContextAvailable
}
#elseif os(iOS)
UIGraphicsBeginImageContext(
UIGraphicsBeginImageContextWithOptions(
CGSize(
width: width,
height: height
)
),
true,
0
)
guard let context = UIGraphicsGetCurrentContext() else {
throw ImageMakingError.noContextAvailable
Expand Down
2 changes: 1 addition & 1 deletion Sources/Csv2ImgCore/ImageRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public class ImageRenderer {
context: CGContext, text: String, frame: CGRect, style: Csv.Column.Style, fontSize: CGFloat
) {
let attributes: [NSAttributedString.Key: Any] = [
.font: Font.systemFont(ofSize: fontSize),
.font: text.getFont(ofSize: fontSize),
.foregroundColor: style.displayableColor(),
]

Expand Down
9 changes: 2 additions & 7 deletions Sources/Csv2ImgCore/PdfMaker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -591,9 +591,7 @@ extension PdfMaker {
let str = NSAttributedString(
string: text,
attributes: [
.font: Font.systemFont(
ofSize: fontSize
),
.font: text.getFont(ofSize: fontSize),
.foregroundColor: style.displayableColor(),
]
)
Expand Down Expand Up @@ -717,10 +715,7 @@ extension PdfMaker {
let str = NSAttributedString(
string: column.name,
attributes: [
.font: Font.systemFont(
ofSize: fontSize,
weight: .bold
),
.font: column.name.getFont(ofSize: fontSize),
.foregroundColor: column.style.displayableColor(),
]
)
Expand Down
38 changes: 21 additions & 17 deletions Sources/Csv2ImgCore/String+Ex.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import Foundation

#if canImport(AppKit)
import AppKit
typealias Font = NSFont
#elseif canImport(UIKit)
import UIKit
typealias Font = UIFont
#endif
import CoreText

extension String {
func getSize(
fontSize: Double
) -> CGSize {
(self as NSString)
.size(
withAttributes: [
.font: Font.systemFont(
ofSize: fontSize,
weight: .bold
)
]
)
// Calculate the size of the string using CoreText
let font = getFont(ofSize: fontSize)
let attrString = NSAttributedString(
string: self,
attributes: [
.font: font
])
let size = attrString.size()
return CGSize(
width: Int(size.width),
height: Int(size.height)
)
}

func getFont(ofSize fontSize: CGFloat) -> CTFont {
CTFontCreateWithName(
"San Francisco" as CFString,
fontSize,
nil
)
}
}

Expand Down
198 changes: 141 additions & 57 deletions Tests/Csv2ImgCoreTests/ImageMakerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,63 +20,124 @@ class ImageMakerTests: XCTestCase {
Csv.Column.Style(color: Color.blue.cgColor),
]
)
let expectedImageRepresentation = CsvImageRepresentation(
width: 546,
height: 160,
backgroundColor: CGColor(red: 0.980392, green: 0.980392, blue: 0.980392, alpha: 1),
fontSize: 12.0,
columns: [
CsvImageRepresentation.ColumnRepresentation(
name: "name",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 8.0, y: 12.0, width: 158.0, height: 25.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "beginnerValue",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 174.0, y: 12.0, width: 106.0, height: 25.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "middleValue",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 288.0, y: 12.0, width: 93.0, height: 25.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "expertValue",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 389.0, y: 12.0, width: 92.0, height: 25.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "unit",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 489.0, y: 12.0, width: 49.0, height: 25.0)),
],
rows: [
CsvImageRepresentation.RowRepresentation(
values: ["Requirements Analysis", "1.00", "1.00", "1.00", "H"],
frames: [
CGRect(x: 8.0, y: 49.0, width: 158.0, height: 25.0),
CGRect(x: 174.0, y: 49.0, width: 106.0, height: 25.0),
CGRect(x: 288.0, y: 49.0, width: 93.0, height: 25.0),
CGRect(x: 389.0, y: 49.0, width: 92.0, height: 25.0),
CGRect(x: 489.0, y: 49.0, width: 49.0, height: 25.0),
]),
CsvImageRepresentation.RowRepresentation(
values: ["Concept Design", "0.10", "0.50", "1.00", "H"],
frames: [
CGRect(x: 8.0, y: 86.0, width: 158.0, height: 25.0),
CGRect(x: 174.0, y: 86.0, width: 106.0, height: 25.0),
CGRect(x: 288.0, y: 86.0, width: 93.0, height: 25.0),
CGRect(x: 389.0, y: 86.0, width: 92.0, height: 25.0),
CGRect(x: 489.0, y: 86.0, width: 49.0, height: 25.0),
]),
CsvImageRepresentation.RowRepresentation(
values: ["Detail Design", "0.10", "0.50", "1.00", "page"],
frames: [
CGRect(x: 8.0, y: 123.0, width: 158.0, height: 25.0),
CGRect(x: 174.0, y: 123.0, width: 106.0, height: 25.0),
CGRect(x: 288.0, y: 123.0, width: 93.0, height: 25.0),
CGRect(x: 389.0, y: 123.0, width: 92.0, height: 25.0),
CGRect(x: 489.0, y: 123.0, width: 49.0, height: 25.0),
]),
]
)
let expectedImageRepresentation: CsvImageRepresentation
#if os(iOS)
expectedImageRepresentation = CsvImageRepresentation(
width: 500,
height: 152,
backgroundColor: CGColor(red: 0.980392, green: 0.980392, blue: 0.980392, alpha: 1),
fontSize: 12.0,
columns: [
CsvImageRepresentation.ColumnRepresentation(
name: "name",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 8.0, y: 12.0, width: 142.0, height: 23.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "beginnerValue",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 158.0, y: 12.0, width: 96.0, height: 23.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "middleValue",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 262.0, y: 12.0, width: 85.0, height: 23.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "expertValue",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 355.0, y: 12.0, width: 83.0, height: 23.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "unit",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 446.0, y: 12.0, width: 46.0, height: 23.0)),
],
rows: [
CsvImageRepresentation.RowRepresentation(
values: ["Requirements Analysis", "1.00", "1.00", "1.00", "H"],
frames: [
CGRect(x: 8.0, y: 47.0, width: 142.0, height: 23.0),
CGRect(x: 158.0, y: 47.0, width: 96.0, height: 23.0),
CGRect(x: 262.0, y: 47.0, width: 85.0, height: 23.0),
CGRect(x: 355.0, y: 47.0, width: 83.0, height: 23.0),
CGRect(x: 446.0, y: 47.0, width: 46.0, height: 23.0),
]),
CsvImageRepresentation.RowRepresentation(
values: ["Concept Design", "0.10", "0.50", "1.00", "H"],
frames: [
CGRect(x: 8.0, y: 82.0, width: 142.0, height: 23.0),
CGRect(x: 158.0, y: 82.0, width: 96.0, height: 23.0),
CGRect(x: 262.0, y: 82.0, width: 85.0, height: 23.0),
CGRect(x: 355.0, y: 82.0, width: 83.0, height: 23.0),
CGRect(x: 446.0, y: 82.0, width: 46.0, height: 23.0),
]),
CsvImageRepresentation.RowRepresentation(
values: ["Detail Design", "0.10", "0.50", "1.00", "page"],
frames: [
CGRect(x: 8.0, y: 117.0, width: 142.0, height: 23.0),
CGRect(x: 158.0, y: 117.0, width: 96.0, height: 23.0),
CGRect(x: 262.0, y: 117.0, width: 85.0, height: 23.0),
CGRect(x: 355.0, y: 117.0, width: 83.0, height: 23.0),
CGRect(x: 446.0, y: 117.0, width: 46.0, height: 23.0),
]),
]
)
#elseif os(macOS)
expectedImageRepresentation = CsvImageRepresentation(
width: 500,
height: 148,
backgroundColor: CGColor(red: 0.980392, green: 0.980392, blue: 0.980392, alpha: 1),
fontSize: 12.0,
columns: [
CsvImageRepresentation.ColumnRepresentation(
name: "name",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 8.0, y: 12.0, width: 142.0, height: 22.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "beginnerValue",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 158.0, y: 12.0, width: 96.0, height: 22.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "middleValue",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 262.0, y: 12.0, width: 85.0, height: 22.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "expertValue",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 355.0, y: 12.0, width: 83.0, height: 22.0)),
CsvImageRepresentation.ColumnRepresentation(
name: "unit",
style: Csv.Column.Style(color: Color.blue.cgColor, applyOnlyColumn: false),
frame: CGRect(x: 446.0, y: 12.0, width: 46.0, height: 22.0)),
],
rows: [
CsvImageRepresentation.RowRepresentation(
values: ["Requirements Analysis", "1.00", "1.00", "1.00", "H"],
frames: [
CGRect(x: 8.0, y: 46.0, width: 142.0, height: 22.0),
CGRect(x: 158.0, y: 46.0, width: 96.0, height: 22.0),
CGRect(x: 262.0, y: 46.0, width: 85.0, height: 22.0),
CGRect(x: 355.0, y: 46.0, width: 83.0, height: 22.0),
CGRect(x: 446.0, y: 46.0, width: 46.0, height: 22.0),
]),
CsvImageRepresentation.RowRepresentation(
values: ["Concept Design", "0.10", "0.50", "1.00", "H"],
frames: [
CGRect(x: 8.0, y: 80.0, width: 142.0, height: 22.0),
CGRect(x: 158.0, y: 80.0, width: 96.0, height: 22.0),
CGRect(x: 262.0, y: 80.0, width: 85.0, height: 22.0),
CGRect(x: 355.0, y: 80.0, width: 83.0, height: 22.0),
CGRect(x: 446.0, y: 80.0, width: 46.0, height: 22.0),
]),
CsvImageRepresentation.RowRepresentation(
values: ["Detail Design", "0.10", "0.50", "1.00", "page"],
frames: [
CGRect(x: 8.0, y: 114.0, width: 142.0, height: 22.0),
CGRect(x: 158.0, y: 114.0, width: 96.0, height: 22.0),
CGRect(x: 262.0, y: 114.0, width: 85.0, height: 22.0),
CGRect(x: 355.0, y: 114.0, width: 83.0, height: 22.0),
CGRect(x: 446.0, y: 114.0, width: 46.0, height: 22.0),
]),
]
)
#endif
let imageMaker = ImageMaker(maximumRowCount: nil, fontSize: 12)
let columns = await csv.columns
let rows = await csv.rows
Expand Down Expand Up @@ -124,4 +185,27 @@ class ImageMakerTests: XCTestCase {
XCTAssertEqual(actual.frames, expected.frames)
}
}

func testMakeImage() async throws {
// Given
let outputFileURL = getRelativeFilePathFromPackageSource(
path: "Fixtures/outputs/category.png"
)
let csv = Csv.loadFromString(
"""
name,beginnerValue,middleValue,expertValue,unit
Requirements Analysis,1.00,1.00,1.00,H
Concept Design,0.10,0.50,1.00,H
Detail Design,0.10,0.50,1.00,page
"""
)
let imageMaker = ImageMaker(maximumRowCount: nil, fontSize: 12)
let columns = await csv.columns
let rows = await csv.rows
// When
let image = try imageMaker.make(columns: columns, rows: rows) { double in
}
// Then
try image.convertToData()?.write(to: outputFileURL, options: .atomic)
}
}
28 changes: 28 additions & 0 deletions Tests/Csv2ImgCoreTests/String+ExTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// String+ExTests.swift
// Csv2Img
//
// Created by Fumiya Tanaka on 2024/10/19.
//

import XCTest

@testable import Csv2ImgCore

class StringExtensionTests: XCTestCase {
func testGetSize() throws {
let sut = "Hello World"
let fontSize: CGFloat = 12

// Because font system is different between iOS and macOS,
// calculation logic might be different and result font size differs.
#if os(iOS)
let expected = CGSize(width: 61, height: 13)
#elseif os(macOS)
let expected = CGSize(width: 61, height: 12)
#endif

let actual = sut.getSize(fontSize: fontSize)
XCTAssertEqual(actual, expected)
}
}

0 comments on commit 7849196

Please sign in to comment.