Commit a6f5c1af90b631809dcc8b1d3b8076499446fd08
1 parent
530f6214
开发了安卓基座
Showing
99 changed files
with
6353 additions
and
109 deletions
Footsafety-Android @ f7a136823ce
| 1 | +Subproject commit f7a136823cefd858d2d25a5e26e6a684cc1cbdcb | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/AndroidSDKAPI.7z
0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo-V3.3.1.apk
0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/.gitignore
0 → 100755
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/.gitignore
0 → 100755
| 1 | +/build | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/build.gradle
0 → 100755
| 1 | +apply plugin: 'com.android.application' | |
| 2 | + | |
| 3 | +android { | |
| 4 | + compileSdkVersion 29 | |
| 5 | + buildToolsVersion "29.0.2" | |
| 6 | + defaultConfig { | |
| 7 | + applicationId "com.printer.tscdemo" | |
| 8 | + minSdkVersion 18 | |
| 9 | + targetSdkVersion 29 | |
| 10 | + versionCode 32 | |
| 11 | + versionName "3.3.1" | |
| 12 | + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |
| 13 | + } | |
| 14 | + android.applicationVariants.all { variant -> | |
| 15 | + variant.outputs.all { | |
| 16 | + if (variant.buildType.name.equals("release")) { | |
| 17 | + outputFileName = "TscDemo-release-${variant.versionName}.apk" | |
| 18 | + } else { | |
| 19 | + outputFileName = "TscDemo-debug-${variant.versionName}.apk" | |
| 20 | + } | |
| 21 | + } | |
| 22 | + } | |
| 23 | + buildTypes { | |
| 24 | + release { | |
| 25 | + minifyEnabled false | |
| 26 | + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | |
| 27 | + } | |
| 28 | + } | |
| 29 | +} | |
| 30 | +repositories { | |
| 31 | + flatDir { | |
| 32 | + dirs 'libs' // 声明添加libs文件夹为库 | |
| 33 | + } | |
| 34 | +} | |
| 35 | + | |
| 36 | +dependencies { | |
| 37 | + implementation fileTree(include: ['*.jar'], dir: 'libs') | |
| 38 | + androidTestImplementation 'androidx.test:runner:1.2.0' | |
| 39 | + implementation 'androidx.appcompat:appcompat:1.0.2' | |
| 40 | + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' | |
| 41 | + implementation 'com.gainscha:jzint:0.4.1' | |
| 42 | + implementation files('libs/SDKLib.jar') | |
| 43 | + testImplementation 'junit:junit:4.12' | |
| 44 | + androidTestImplementation 'androidx.test:runner:1.1.1' | |
| 45 | + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' | |
| 46 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/libs/SDKLib.jar
0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/proguard-rules.pro
0 → 100755
| 1 | +# Add project specific ProGuard rules here. | |
| 2 | +# You can control the set of applied configuration files using the | |
| 3 | +# proguardFiles setting in build.gradle. | |
| 4 | +# | |
| 5 | +# For more details, see | |
| 6 | +# http://developer.android.com/guide/developing/tools/proguard.html | |
| 7 | + | |
| 8 | +# If your project uses WebView with JS, uncomment the following | |
| 9 | +# and specify the fully qualified class name to the JavaScript interface | |
| 10 | +# class: | |
| 11 | +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | |
| 12 | +# public *; | |
| 13 | +#} | |
| 14 | + | |
| 15 | +# Uncomment this to preserve the line number information for | |
| 16 | +# debugging stack traces. | |
| 17 | +#-keepattributes SourceFile,LineNumberTable | |
| 18 | + | |
| 19 | +# If you keep the line number information, uncomment this to | |
| 20 | +# hide the original source file name. | |
| 21 | +#-renamesourcefileattribute SourceFile | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/androidTest/java/com/printer/tscdemo/ExampleInstrumentedTest.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | + | |
| 5 | +import androidx.test.platform.app.InstrumentationRegistry; | |
| 6 | +import androidx.test.runner.AndroidJUnit4; | |
| 7 | + | |
| 8 | +import org.junit.Test; | |
| 9 | +import org.junit.runner.RunWith; | |
| 10 | + | |
| 11 | +import static org.junit.Assert.*; | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * Instrumented test, which will execute on an Android device. | |
| 15 | + * | |
| 16 | + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | |
| 17 | + */ | |
| 18 | +@RunWith(AndroidJUnit4.class) | |
| 19 | +public class ExampleInstrumentedTest { | |
| 20 | + @Test | |
| 21 | + public void useAppContext() { | |
| 22 | + // Context of the app under test. | |
| 23 | + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); | |
| 24 | + | |
| 25 | + assertEquals("com.printer.tscdemo", appContext.getPackageName()); | |
| 26 | + } | |
| 27 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/AndroidManifest.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + package="com.printer.tscdemo"> | |
| 4 | + | |
| 5 | + <uses-permission android:name="android.permission.BLUETOOTH" /> | |
| 6 | + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> | |
| 7 | + <uses-permission android:name="android.permission.INTERNET" /> | |
| 8 | + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | |
| 9 | + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> | |
| 10 | + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> | |
| 11 | + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> | |
| 12 | + | |
| 13 | + <application | |
| 14 | + android:allowBackup="true" | |
| 15 | + android:icon="@mipmap/ic_launcher" | |
| 16 | + android:label="@string/app_name" | |
| 17 | + android:roundIcon="@mipmap/ic_launcher_round" | |
| 18 | + android:supportsRtl="true" | |
| 19 | + android:theme="@style/AppTheme"> | |
| 20 | + | |
| 21 | + <activity android:name=".MainActivity"> | |
| 22 | + <intent-filter> | |
| 23 | + <action android:name="android.intent.action.MAIN" /> | |
| 24 | + <action android:name="android.intent.action.VIEW" /> | |
| 25 | + | |
| 26 | + <category android:name="android.intent.category.LAUNCHER" /> | |
| 27 | + </intent-filter> | |
| 28 | + </activity> | |
| 29 | + <activity android:name=".BlueToothDeviceActivity"/> | |
| 30 | + <activity android:name=".UsbDeviceActivity" /> | |
| 31 | + <activity android:name=".SerialPortDeviceActivity" android:theme="@style/Theme.AppCompat.DayNight.Dialog" /> | |
| 32 | + <activity android:name=".WifiDeviceActivity" android:theme="@style/Theme.AppCompat.DayNight.Dialog" /> | |
| 33 | + </application> | |
| 34 | + | |
| 35 | +</manifest> | |
| 0 | 36 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/assets/TSPL.bin
0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/assets/WalmartFile.pdf
0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/assets/esc.pdf
0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/BlueToothDeviceActivity.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import android.Manifest; | |
| 4 | +import android.app.Activity; | |
| 5 | +import android.app.AlertDialog; | |
| 6 | +import android.bluetooth.BluetoothAdapter; | |
| 7 | +import android.bluetooth.BluetoothDevice; | |
| 8 | +import android.content.BroadcastReceiver; | |
| 9 | +import android.content.Context; | |
| 10 | +import android.content.DialogInterface; | |
| 11 | +import android.content.Intent; | |
| 12 | +import android.content.IntentFilter; | |
| 13 | +import android.location.LocationManager; | |
| 14 | +import android.os.Build; | |
| 15 | +import android.os.Bundle; | |
| 16 | +import android.provider.Settings; | |
| 17 | +import android.util.Log; | |
| 18 | +import android.view.View; | |
| 19 | +import android.view.Window; | |
| 20 | +import android.widget.AdapterView; | |
| 21 | +import android.widget.Button; | |
| 22 | +import android.widget.ListView; | |
| 23 | +import android.widget.Toast; | |
| 24 | +import androidx.appcompat.app.AppCompatActivity; | |
| 25 | +import com.printer.tscdemo.bean.BluetoothParameter; | |
| 26 | +import java.util.ArrayList; | |
| 27 | +import java.util.Collections; | |
| 28 | +import java.util.Comparator; | |
| 29 | +import java.util.List; | |
| 30 | + | |
| 31 | +public class BlueToothDeviceActivity extends AppCompatActivity { | |
| 32 | + private String TAG= BlueToothDeviceActivity.class.getSimpleName(); | |
| 33 | + private ListView lvDevices = null; | |
| 34 | + private BluetoothDeviceAdapter adapter; | |
| 35 | + //已配对列表 | |
| 36 | + private List<BluetoothParameter> pairedDevices =new ArrayList<>(); | |
| 37 | + //新设备列表 | |
| 38 | + private List<BluetoothParameter> newDevices = new ArrayList<>(); | |
| 39 | + private BluetoothAdapter mBluetoothAdapter; | |
| 40 | + public static final String EXTRA_DEVICE_ADDRESS ="address"; | |
| 41 | + public static final int REQUEST_ENABLE_BT = 2; | |
| 42 | + public static final int REQUEST_ENABLE_GPS = 3; | |
| 43 | + private PermissionUtils permissionUtils; | |
| 44 | + private LocationManager manager ; | |
| 45 | + private Button btn_search; | |
| 46 | + /** | |
| 47 | + * changes the title when discovery is finished | |
| 48 | + */ | |
| 49 | + private final BroadcastReceiver mFindBlueToothReceiver = new BroadcastReceiver() { | |
| 50 | + @Override | |
| 51 | + public void onReceive(Context context, Intent intent) { | |
| 52 | + String action = intent.getAction(); | |
| 53 | + // When discovery finds a device | |
| 54 | + if (BluetoothDevice.ACTION_FOUND.equals(action)) { | |
| 55 | + // Get the BluetoothDevice object from the Intent | |
| 56 | + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); | |
| 57 | + // If it's already paired, skip it, because it's been listed | |
| 58 | + // already | |
| 59 | + BluetoothParameter parameter = new BluetoothParameter(); | |
| 60 | + int rssi = intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI);//获取蓝牙信号强度 | |
| 61 | + if (device != null && device.getName() != null) { | |
| 62 | + parameter.setBluetoothName(device.getName()); | |
| 63 | + } else { | |
| 64 | + parameter.setBluetoothName("unKnow"); | |
| 65 | + } | |
| 66 | + parameter.setBluetoothMac(device.getAddress()); | |
| 67 | + parameter.setBluetoothStrength(rssi+ ""); | |
| 68 | + Log.e(TAG,"\nBlueToothName:\t"+device.getName()+"\nMacAddress:\t"+device.getAddress()+"\nrssi:\t"+rssi); | |
| 69 | + if (device.getBondState() != BluetoothDevice.BOND_BONDED) {//未配对 | |
| 70 | + for (BluetoothParameter p:newDevices) { | |
| 71 | + if (p.getBluetoothMac().equals(parameter.getBluetoothMac())){//防止重复添加 | |
| 72 | + return; | |
| 73 | + } | |
| 74 | + } | |
| 75 | + newDevices.add(parameter); | |
| 76 | + Collections.sort(newDevices,new Signal()); | |
| 77 | + adapter.notifyDataSetChanged(); | |
| 78 | + } else {//更新已配对蓝牙 | |
| 79 | + for (int i = 0; i < pairedDevices.size(); i++) { | |
| 80 | + if (pairedDevices.get(i).getBluetoothMac().equals(parameter.getBluetoothMac())){ | |
| 81 | + pairedDevices.get(i).setBluetoothStrength(parameter.getBluetoothStrength()); | |
| 82 | + adapter.notifyDataSetChanged(); | |
| 83 | + return; | |
| 84 | + } | |
| 85 | + } | |
| 86 | + pairedDevices.add(parameter); | |
| 87 | + adapter.notifyDataSetChanged(); | |
| 88 | + } | |
| 89 | + // When discovery is finished, change the Activity title | |
| 90 | + } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED | |
| 91 | + .equals(action)) { | |
| 92 | + setProgressBarIndeterminateVisibility(false); | |
| 93 | + setTitle(R.string.complete); | |
| 94 | + Log.i("tag", "finish discovery" + (adapter.getCount()-2)); | |
| 95 | + }else if(BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)){ | |
| 96 | + int bluetooth_state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, | |
| 97 | + BluetoothAdapter.ERROR); | |
| 98 | + if (bluetooth_state==BluetoothAdapter.STATE_OFF) {//关闭 | |
| 99 | + finish(); | |
| 100 | + } | |
| 101 | + if (bluetooth_state==BluetoothAdapter.STATE_ON) {//开启 | |
| 102 | + | |
| 103 | + } | |
| 104 | + } | |
| 105 | + } | |
| 106 | + }; | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + // 自定义比较器:按信号强度排序 | |
| 111 | + static class Signal implements Comparator { | |
| 112 | + public int compare(Object object1, Object object2) {// 实现接口中的方法 | |
| 113 | + BluetoothParameter p1 = (BluetoothParameter) object1; // 强制转换 | |
| 114 | + BluetoothParameter p2 = (BluetoothParameter) object2; | |
| 115 | + return p1.getBluetoothStrength().compareTo(p2.getBluetoothStrength()); | |
| 116 | + } | |
| 117 | + } | |
| 118 | + @Override | |
| 119 | + protected void onCreate(Bundle savedInstanceState) { | |
| 120 | + super.onCreate(savedInstanceState); | |
| 121 | + // Request progress bar | |
| 122 | + //启用窗口特征 | |
| 123 | + requestWindowFeature(Window.FEATURE_PROGRESS); | |
| 124 | + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); | |
| 125 | + setContentView(R.layout.activity_bluetooth); | |
| 126 | + setTitle(getString(R.string.blue_label)); | |
| 127 | + initView(); | |
| 128 | + initBluetooth(); | |
| 129 | + initBroadcast(); | |
| 130 | + } | |
| 131 | + | |
| 132 | + | |
| 133 | + /** | |
| 134 | + * 搜索蓝牙 | |
| 135 | + */ | |
| 136 | + public void searchBlueTooth(){ | |
| 137 | + btn_search.setVisibility(View.GONE); | |
| 138 | + setTitle(getString(R.string.searching)); | |
| 139 | + setProgressBarIndeterminateVisibility(true); | |
| 140 | + mBluetoothAdapter.startDiscovery(); | |
| 141 | + } | |
| 142 | + /** | |
| 143 | + * 初始化广播 | |
| 144 | + */ | |
| 145 | + private void initBroadcast() { | |
| 146 | + try { | |
| 147 | + // Register for broadcasts when a device is discovered | |
| 148 | + IntentFilter filter = new IntentFilter(); | |
| 149 | + filter.addAction(BluetoothDevice.ACTION_FOUND); | |
| 150 | + filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); | |
| 151 | + // Register for broadcasts when discovery has finished | |
| 152 | + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//蓝牙状态改变 | |
| 153 | + this.registerReceiver(mFindBlueToothReceiver, filter); | |
| 154 | + }catch (Exception e){ | |
| 155 | + | |
| 156 | + } | |
| 157 | + } | |
| 158 | + | |
| 159 | + private void initView() { | |
| 160 | + lvDevices=(ListView)findViewById(R.id.lv_devices); | |
| 161 | + btn_search=(Button)findViewById(R.id.btn_search); | |
| 162 | + btn_search.setOnClickListener(new View.OnClickListener() { | |
| 163 | + @Override | |
| 164 | + public void onClick(View v) { | |
| 165 | + initBluetooth(); | |
| 166 | + } | |
| 167 | + }); | |
| 168 | + adapter = new BluetoothDeviceAdapter(pairedDevices,newDevices, BlueToothDeviceActivity.this); | |
| 169 | + lvDevices.setAdapter(adapter); | |
| 170 | + lvDevices.setOnItemClickListener(new AdapterView.OnItemClickListener() { | |
| 171 | + @Override | |
| 172 | + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { | |
| 173 | + //点击已配对设备、新设备title不响应 | |
| 174 | + if (position == 0 || position == pairedDevices.size() + 1) { | |
| 175 | + return; | |
| 176 | + } | |
| 177 | + String mac=null; | |
| 178 | + if (position <= pairedDevices.size()) {//点击已配对设备列表 | |
| 179 | + mac= pairedDevices.get(position-1).getBluetoothMac(); | |
| 180 | + } | |
| 181 | + else {//点击新设备列表 | |
| 182 | + mac=newDevices.get(position-2- pairedDevices.size()).getBluetoothMac(); | |
| 183 | + } | |
| 184 | + mBluetoothAdapter.cancelDiscovery(); | |
| 185 | + | |
| 186 | + // Create the result Intent and include the MAC address | |
| 187 | + Intent intent = new Intent(); | |
| 188 | + intent.putExtra(EXTRA_DEVICE_ADDRESS, mac); | |
| 189 | + // Set result and finish this Activity | |
| 190 | + setResult(Activity.RESULT_OK, intent); | |
| 191 | + finish(); | |
| 192 | + | |
| 193 | + } | |
| 194 | + }); | |
| 195 | + } | |
| 196 | + private void initBluetooth(){ | |
| 197 | + // Get the local Bluetooth adapter | |
| 198 | + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); | |
| 199 | + // If the adapter is null, then Bluetooth is not supported | |
| 200 | + if (mBluetoothAdapter == null) { | |
| 201 | + Toast.makeText(this, "Bluetooth is not supported by the device",Toast.LENGTH_LONG).show(); | |
| 202 | + } else { | |
| 203 | + // If BT is not on, request that it be enabled. | |
| 204 | + // setupChat() will then be called during onActivityResult | |
| 205 | + if (!mBluetoothAdapter.isEnabled()) { | |
| 206 | + Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); | |
| 207 | + startActivityForResult(enableIntent, REQUEST_ENABLE_BT); | |
| 208 | + } else { | |
| 209 | + manager= (LocationManager)BlueToothDeviceActivity.this .getSystemService(LOCATION_SERVICE); | |
| 210 | + permissionUtils=new PermissionUtils(BlueToothDeviceActivity.this); | |
| 211 | + permissionUtils.requestPermissions(getString(R.string.permission), | |
| 212 | + new PermissionUtils.PermissionListener(){ | |
| 213 | + @Override | |
| 214 | + public void doAfterGrand(String... permission) { | |
| 215 | + if ((Build.VERSION.SDK_INT>=29)&&! manager.isProviderEnabled(LocationManager.GPS_PROVIDER)){ | |
| 216 | + AlertDialog alertDialog = new AlertDialog.Builder(BlueToothDeviceActivity.this) | |
| 217 | + .setTitle(getString(R.string.tip)) | |
| 218 | + .setMessage(getString(R.string.gps_permission)) | |
| 219 | + .setIcon(R.mipmap.ic_launcher) | |
| 220 | + .setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {//添加"Yes"按钮 | |
| 221 | + @Override | |
| 222 | + public void onClick(DialogInterface dialogInterface, int i) { | |
| 223 | + Intent intent = new Intent(); | |
| 224 | + intent.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS); | |
| 225 | + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
| 226 | + startActivityForResult(intent,REQUEST_ENABLE_GPS); | |
| 227 | + } | |
| 228 | + }) | |
| 229 | + .create(); | |
| 230 | + alertDialog.show(); | |
| 231 | + }else { | |
| 232 | + searchBlueTooth(); | |
| 233 | + } | |
| 234 | + } | |
| 235 | + @Override | |
| 236 | + public void doAfterDenied(String... permission) { | |
| 237 | + for (String p:permission) { | |
| 238 | + switch (p){ | |
| 239 | + case Manifest.permission.ACCESS_FINE_LOCATION: | |
| 240 | + Utils.shortToast(BlueToothDeviceActivity.this,getString(R.string.no_permission)); | |
| 241 | + break; | |
| 242 | + | |
| 243 | + } | |
| 244 | + } | |
| 245 | + } | |
| 246 | + }, Manifest.permission.ACCESS_FINE_LOCATION); | |
| 247 | + } | |
| 248 | + } | |
| 249 | + } | |
| 250 | + | |
| 251 | + | |
| 252 | + @Override | |
| 253 | + protected void onActivityResult(int requestCode, int resultCode, Intent data) { | |
| 254 | + super.onActivityResult(requestCode, resultCode, data); | |
| 255 | + if (requestCode == REQUEST_ENABLE_BT) { | |
| 256 | + if (resultCode == Activity.RESULT_OK) { | |
| 257 | + // bluetooth is opened | |
| 258 | + initBluetooth(); | |
| 259 | + } else { | |
| 260 | + // bluetooth is not open | |
| 261 | + Toast.makeText(this, R.string.bluetooth_is_not_enabled, Toast.LENGTH_SHORT).show(); | |
| 262 | + finish(); | |
| 263 | + } | |
| 264 | + }else if (requestCode==REQUEST_ENABLE_GPS){ | |
| 265 | + if (resultCode == Activity.RESULT_OK) { | |
| 266 | + // bluetooth is opened | |
| 267 | + initBluetooth(); | |
| 268 | + } else { | |
| 269 | + // bluetooth is not open | |
| 270 | + } | |
| 271 | + } | |
| 272 | + } | |
| 273 | + @Override | |
| 274 | + protected void onDestroy() { | |
| 275 | + super.onDestroy(); | |
| 276 | + try { | |
| 277 | + // Make sure we're not doing discovery anymore | |
| 278 | + if (mBluetoothAdapter != null) { | |
| 279 | + mBluetoothAdapter.cancelDiscovery(); | |
| 280 | + } | |
| 281 | + // Unregister broadcast listeners | |
| 282 | + if (mFindBlueToothReceiver != null) { | |
| 283 | + unregisterReceiver(mFindBlueToothReceiver); | |
| 284 | + } | |
| 285 | + }catch (Exception e){ | |
| 286 | + | |
| 287 | + } | |
| 288 | + } | |
| 289 | +} | |
| 0 | 290 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/BluetoothDeviceAdapter.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.view.Gravity; | |
| 5 | +import android.view.LayoutInflater; | |
| 6 | +import android.view.View; | |
| 7 | +import android.view.ViewGroup; | |
| 8 | +import android.widget.BaseAdapter; | |
| 9 | +import android.widget.TextView; | |
| 10 | + | |
| 11 | +import com.printer.tscdemo.bean.BluetoothParameter; | |
| 12 | + | |
| 13 | +import java.util.List; | |
| 14 | + | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * 作者: Circle | |
| 18 | + * 创造于 2018/5/24. | |
| 19 | + */ | |
| 20 | +public class BluetoothDeviceAdapter extends BaseAdapter { | |
| 21 | + | |
| 22 | + private List<BluetoothParameter> pairedDevices; | |
| 23 | + private List<BluetoothParameter> newDevices; | |
| 24 | + private Context mContext; | |
| 25 | + private static final int TITLE = 0; | |
| 26 | + private static final int CONTENT = 1; | |
| 27 | + public BluetoothDeviceAdapter(List<BluetoothParameter> pairedDevices, List<BluetoothParameter> newDevices, Context context) { | |
| 28 | + this.pairedDevices = pairedDevices; | |
| 29 | + this.newDevices = newDevices; | |
| 30 | + this.mContext = context; | |
| 31 | + } | |
| 32 | + | |
| 33 | + @Override | |
| 34 | + public int getCount() { | |
| 35 | + return pairedDevices.size() + newDevices.size() + 2; | |
| 36 | + } | |
| 37 | + | |
| 38 | + @Override | |
| 39 | + public Object getItem(int position) { | |
| 40 | + return position; | |
| 41 | + } | |
| 42 | + | |
| 43 | + @Override | |
| 44 | + public long getItemId(int position) { | |
| 45 | + return position; | |
| 46 | + } | |
| 47 | + | |
| 48 | + @Override | |
| 49 | + public View getView(int position, View convertView, ViewGroup parent) { | |
| 50 | + switch (getItemViewType(position)) { | |
| 51 | + case TITLE: | |
| 52 | + convertView = LayoutInflater.from(mContext).inflate(R.layout.text_item, parent, false); | |
| 53 | + TextView tv_title = (TextView) convertView.findViewById(R.id.text); | |
| 54 | + tv_title.setTextColor(mContext.getResources().getColor(R.color.colorAccent)); | |
| 55 | + tv_title.setGravity(Gravity.LEFT); | |
| 56 | + if (position == 0) { | |
| 57 | + tv_title.setText(mContext.getResources().getString(R.string.paired)); | |
| 58 | + } else { | |
| 59 | + tv_title.setText(mContext.getResources().getString(R.string.unpaired)); | |
| 60 | + } | |
| 61 | + break; | |
| 62 | + case CONTENT: | |
| 63 | + BluetoothParameter bluetoothParameter = null; | |
| 64 | + if (position < pairedDevices.size() + 1) { | |
| 65 | + bluetoothParameter = pairedDevices.get(position - 1); | |
| 66 | + } | |
| 67 | + if (position > pairedDevices.size()+1 && newDevices.size() > 0) { | |
| 68 | + bluetoothParameter = newDevices.get(position - pairedDevices.size() - 2); | |
| 69 | + } | |
| 70 | + | |
| 71 | + if (bluetoothParameter!=null) { | |
| 72 | + convertView = LayoutInflater.from(mContext).inflate(R.layout.bluetooth_list_item, parent, false); | |
| 73 | + TextView tvName = (TextView) convertView.findViewById(R.id.b_name); | |
| 74 | + TextView tvMac = (TextView) convertView.findViewById(R.id.b_mac); | |
| 75 | + TextView tvStrength = (TextView) convertView.findViewById(R.id.b_info); | |
| 76 | + tvName.setText(bluetoothParameter.getBluetoothName()); | |
| 77 | + tvMac.setText(bluetoothParameter.getBluetoothMac()); | |
| 78 | + tvStrength.setText(bluetoothParameter.getBluetoothStrength()); | |
| 79 | + } | |
| 80 | + break; | |
| 81 | + } | |
| 82 | + | |
| 83 | + return convertView; | |
| 84 | + } | |
| 85 | + | |
| 86 | + @Override | |
| 87 | + public int getViewTypeCount() { | |
| 88 | + return 2; | |
| 89 | + } | |
| 90 | + | |
| 91 | + @Override | |
| 92 | + public int getItemViewType(int position) { | |
| 93 | + if ((position == pairedDevices.size()+1) || (position == 0)) { | |
| 94 | + return TITLE; | |
| 95 | + } else { | |
| 96 | + return CONTENT; | |
| 97 | + } | |
| 98 | + } | |
| 99 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/MainActivity.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import androidx.annotation.NonNull; | |
| 4 | +import androidx.annotation.Nullable; | |
| 5 | +import androidx.appcompat.app.AppCompatActivity; | |
| 6 | + | |
| 7 | +import android.Manifest; | |
| 8 | +import android.app.Activity; | |
| 9 | +import android.app.AlertDialog; | |
| 10 | +import android.content.Context; | |
| 11 | +import android.content.DialogInterface; | |
| 12 | +import android.content.Intent; | |
| 13 | +import android.graphics.Bitmap; | |
| 14 | +import android.graphics.BitmapFactory; | |
| 15 | +import android.hardware.usb.UsbDevice; | |
| 16 | +import android.os.Bundle; | |
| 17 | +import android.os.Handler; | |
| 18 | +import android.os.Looper; | |
| 19 | +import android.os.Message; | |
| 20 | +import android.os.SystemClock; | |
| 21 | +import android.text.TextUtils; | |
| 22 | +import android.util.Log; | |
| 23 | +import android.view.View; | |
| 24 | +import android.widget.CheckBox; | |
| 25 | +import android.widget.ImageView; | |
| 26 | +import android.widget.Spinner; | |
| 27 | +import android.widget.TextView; | |
| 28 | +import android.widget.Toast; | |
| 29 | +import com.gprinter.bean.PrinterDevices; | |
| 30 | +import com.gprinter.command.LabelCommand; | |
| 31 | +import com.gprinter.utils.CallbackListener; | |
| 32 | +import com.gprinter.utils.Command; | |
| 33 | +import com.gprinter.utils.ConnMethod; | |
| 34 | +import com.gprinter.utils.LogUtils; | |
| 35 | +import com.gprinter.utils.PDFUtils; | |
| 36 | +import com.gprinter.utils.SDKUtils; | |
| 37 | +import java.io.File; | |
| 38 | +import java.io.FileOutputStream; | |
| 39 | +import java.io.IOException; | |
| 40 | +import java.io.InputStream; | |
| 41 | +import java.io.UnsupportedEncodingException; | |
| 42 | +import java.text.SimpleDateFormat; | |
| 43 | +import java.util.Date; | |
| 44 | +import java.util.Vector; | |
| 45 | + | |
| 46 | +public class MainActivity extends AppCompatActivity implements CallbackListener { | |
| 47 | + TextView tvState; | |
| 48 | + CheckBox swState; | |
| 49 | + Printer printer=null; | |
| 50 | + Context context; | |
| 51 | + Spinner sp_gap; | |
| 52 | + String TAG=MainActivity.class.getSimpleName(); | |
| 53 | + PermissionUtils permissionUtils; | |
| 54 | + Handler handler=new Handler(Looper.getMainLooper()){ | |
| 55 | + @Override | |
| 56 | + public void handleMessage(@NonNull Message msg) { | |
| 57 | + switch (msg.what){ | |
| 58 | + case 0x00: | |
| 59 | + String tip=(String)msg.obj; | |
| 60 | + Toast.makeText(context,tip,Toast.LENGTH_SHORT).show(); | |
| 61 | + break; | |
| 62 | + case 0x01: | |
| 63 | + int status=msg.arg1; | |
| 64 | + if (status==-1){//获取状态失败 | |
| 65 | + AlertDialog alertDialog = new AlertDialog.Builder(context) | |
| 66 | + .setTitle(getString(R.string.tip)) | |
| 67 | + .setMessage(getString(R.string.status_fail)) | |
| 68 | + .setIcon(R.mipmap.ic_launcher) | |
| 69 | + .setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {//添加"Yes"按钮 | |
| 70 | + @Override | |
| 71 | + public void onClick(DialogInterface dialogInterface, int i) { | |
| 72 | + | |
| 73 | + } | |
| 74 | + }) | |
| 75 | + .create(); | |
| 76 | + alertDialog.show(); | |
| 77 | + return; | |
| 78 | + }else if (status==1){ | |
| 79 | + Toast.makeText(context,getString(R.string.status_feed),Toast.LENGTH_SHORT).show(); | |
| 80 | + return; | |
| 81 | + }else if (status==0){//状态正常 | |
| 82 | + Toast.makeText(context,getString(R.string.status_normal),Toast.LENGTH_SHORT).show(); | |
| 83 | + return; | |
| 84 | + }else if (status==-2){//状态缺纸 | |
| 85 | + Toast.makeText(context,getString(R.string.status_out_of_paper),Toast.LENGTH_SHORT).show(); | |
| 86 | + return; | |
| 87 | + }else if (status==-3){//状态开盖 | |
| 88 | + Toast.makeText(context,getString(R.string.status_open),Toast.LENGTH_SHORT).show(); | |
| 89 | + return; | |
| 90 | + }else if (status==-4){ | |
| 91 | + Toast.makeText(context,getString(R.string.status_overheated),Toast.LENGTH_SHORT).show(); | |
| 92 | + return; | |
| 93 | + } | |
| 94 | + break; | |
| 95 | + case 0x02://关闭连接 | |
| 96 | + new Thread(new Runnable() { | |
| 97 | + @Override | |
| 98 | + public void run() { | |
| 99 | + if (printer.getPortManager()!=null){ | |
| 100 | + printer.close(); | |
| 101 | + } | |
| 102 | + } | |
| 103 | + }).start(); | |
| 104 | + | |
| 105 | + tvState.setText(getString(R.string.not_connected)); | |
| 106 | + break; | |
| 107 | + case 0x03: | |
| 108 | + String message=(String)msg.obj; | |
| 109 | + AlertDialog alertDialog = new AlertDialog.Builder(context) | |
| 110 | + .setTitle(getString(R.string.tip)) | |
| 111 | + .setMessage(message) | |
| 112 | + .setIcon(R.mipmap.ic_launcher) | |
| 113 | + .setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {//添加"Yes"按钮 | |
| 114 | + @Override | |
| 115 | + public void onClick(DialogInterface dialogInterface, int i) { | |
| 116 | + | |
| 117 | + } | |
| 118 | + }) | |
| 119 | + .create(); | |
| 120 | + alertDialog.show(); | |
| 121 | + break; | |
| 122 | + } | |
| 123 | + } | |
| 124 | + }; | |
| 125 | + @Override | |
| 126 | + protected void onCreate(Bundle savedInstanceState) { | |
| 127 | + super.onCreate(savedInstanceState); | |
| 128 | + setContentView(R.layout.activity_main); | |
| 129 | + initView(); | |
| 130 | + initPermission(); | |
| 131 | + } | |
| 132 | + | |
| 133 | + /** | |
| 134 | + * 初始化权限 | |
| 135 | + */ | |
| 136 | + private void initPermission() { | |
| 137 | + permissionUtils.requestPermissions(getString(R.string.permission), | |
| 138 | + new PermissionUtils.PermissionListener(){ | |
| 139 | + @Override | |
| 140 | + public void doAfterGrand(String... permission) { | |
| 141 | + | |
| 142 | + } | |
| 143 | + @Override | |
| 144 | + public void doAfterDenied(String... permission) { | |
| 145 | + for (String p:permission) { | |
| 146 | + switch (p){ | |
| 147 | + case Manifest.permission.READ_EXTERNAL_STORAGE: | |
| 148 | + Utils.shortToast(context,getString(R.string.no_read)); | |
| 149 | + break; | |
| 150 | + case Manifest.permission.ACCESS_FINE_LOCATION: | |
| 151 | + Utils.shortToast(context,getString(R.string.no_permission)); | |
| 152 | + break; | |
| 153 | + } | |
| 154 | + } | |
| 155 | + } | |
| 156 | + }, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.ACCESS_FINE_LOCATION); | |
| 157 | + } | |
| 158 | + | |
| 159 | + private void initView() { | |
| 160 | + context=MainActivity.this; | |
| 161 | + permissionUtils=new PermissionUtils(context); | |
| 162 | + setTitle(getString(R.string.app_name)+"V"+Utils.getVersionName(context)); | |
| 163 | + tvState=(TextView)findViewById(R.id.tvState); | |
| 164 | + swState=(CheckBox)findViewById(R.id.swState); | |
| 165 | + sp_gap=(Spinner)findViewById(R.id.sp_gap) ; | |
| 166 | + printer=Printer.getInstance();//获取管理对象 | |
| 167 | + } | |
| 168 | + /** | |
| 169 | + * 断开连接 | |
| 170 | + * @param view | |
| 171 | + */ | |
| 172 | + public void disconnect(View view) { | |
| 173 | + handler.obtainMessage(0x02).sendToTarget(); | |
| 174 | + } | |
| 175 | + | |
| 176 | + /** | |
| 177 | + * 蓝牙设备 | |
| 178 | + * @param view | |
| 179 | + */ | |
| 180 | + public void blueToothDevices(View view) { | |
| 181 | + startActivityForResult(new Intent(context, BlueToothDeviceActivity.class),0x00); | |
| 182 | + } | |
| 183 | + /** | |
| 184 | + * usb设备 | |
| 185 | + * @param view | |
| 186 | + */ | |
| 187 | + public void usbDevices(View view) { | |
| 188 | + startActivityForResult(new Intent(context, UsbDeviceActivity.class),0x01); | |
| 189 | + } | |
| 190 | + /** | |
| 191 | + * wifi接口 | |
| 192 | + * @param view | |
| 193 | + */ | |
| 194 | + public void wifiDevices(View view) { | |
| 195 | + startActivityForResult(new Intent(context, WifiDeviceActivity.class),0x02); | |
| 196 | + } | |
| 197 | + /** | |
| 198 | + * 串口接口 | |
| 199 | + * @param view | |
| 200 | + */ | |
| 201 | + public void serialPortDevices(View view) { | |
| 202 | + startActivityForResult(new Intent(context, SerialPortDeviceActivity.class),0x03); | |
| 203 | + } | |
| 204 | + /** | |
| 205 | + * 打印案例 | |
| 206 | + * @param view | |
| 207 | + */ | |
| 208 | + public void print(View view) { | |
| 209 | + ThreadPoolManager.getInstance().addTask(new Runnable() { | |
| 210 | + @Override | |
| 211 | + public void run() { | |
| 212 | + try { | |
| 213 | + if (printer.getPortManager()==null){ | |
| 214 | + tipsToast(getString(R.string.conn_first)); | |
| 215 | + return; | |
| 216 | + } | |
| 217 | + //打印前后查询打印机状态,部分老款打印机不支持查询请去除下面查询代码 | |
| 218 | + //****************** 查询状态 *************************** | |
| 219 | + if (swState.isChecked()) { | |
| 220 | + Command command = printer.getPortManager().getCommand(); | |
| 221 | + int status = printer.getPrinterState(command,2000); | |
| 222 | + if (status != 0) {//打印机处于不正常状态,则不发送打印任务 | |
| 223 | + Message msg = new Message(); | |
| 224 | + msg.what = 0x01; | |
| 225 | + msg.arg1 = status; | |
| 226 | + handler.sendMessage(msg); | |
| 227 | + return; | |
| 228 | + } | |
| 229 | + } | |
| 230 | + //*************************************************************** | |
| 231 | + boolean result=printer.getPortManager().writeDataImmediately(PrintContent.getLabel(context,sp_gap.getSelectedItemPosition())); | |
| 232 | + if (result) { | |
| 233 | + tipsDialog(getString(R.string.send_success)); | |
| 234 | + }else { | |
| 235 | + tipsDialog(getString(R.string.send_fail)); | |
| 236 | + } | |
| 237 | + LogUtils.e("send result",result); | |
| 238 | + } catch (IOException e) { | |
| 239 | + tipsDialog(getString(R.string.print_fail)+e.getMessage()); | |
| 240 | + }catch (Exception e){ | |
| 241 | + tipsDialog(getString(R.string.print_fail)+e.getMessage()); | |
| 242 | + }finally { | |
| 243 | + if (printer.getPortManager()==null) { | |
| 244 | + printer.close(); | |
| 245 | + } | |
| 246 | + } | |
| 247 | + } | |
| 248 | + }); | |
| 249 | + | |
| 250 | + } | |
| 251 | + | |
| 252 | + /** | |
| 253 | + * 打印xml 布局 | |
| 254 | + * @param view | |
| 255 | + */ | |
| 256 | + public void xml(View view) { | |
| 257 | + ThreadPoolManager.getInstance().addTask(new Runnable() { | |
| 258 | + @Override | |
| 259 | + public void run() { | |
| 260 | + if (printer.getPortManager()==null){ | |
| 261 | + tipsToast(getString(R.string.conn_first)); | |
| 262 | + return; | |
| 263 | + } | |
| 264 | + try { | |
| 265 | + | |
| 266 | + printer.getPortManager().writeDataImmediately(PrintContent.getXmlBitmap(context)); | |
| 267 | + } catch (IOException e) { | |
| 268 | + tipsDialog(getString(R.string.status_error)+e.getMessage()); | |
| 269 | + }catch (Exception e){ | |
| 270 | + tipsDialog(getString(R.string.status_error)+e.getMessage()); | |
| 271 | + } | |
| 272 | + } | |
| 273 | + }); | |
| 274 | + } | |
| 275 | + /** | |
| 276 | + * 打印PDF | |
| 277 | + * @param view | |
| 278 | + */ | |
| 279 | + public void printPDF(View view) { | |
| 280 | + if (!permissionUtils.hasPermissions(context,Manifest.permission.READ_EXTERNAL_STORAGE)){ | |
| 281 | + Utils.shortToast(context,getString(R.string.no_read)); | |
| 282 | + return; | |
| 283 | + } | |
| 284 | + ThreadPoolManager.getInstance().addTask(new Runnable() { | |
| 285 | + @Override | |
| 286 | + public void run() { | |
| 287 | + try { | |
| 288 | + if (printer.getPortManager()==null){ | |
| 289 | + tipsToast(getString(R.string.conn_first)); | |
| 290 | + return; | |
| 291 | + } | |
| 292 | + //打印前后查询打印机状态,部分老款打印机不支持查询请去除下面查询代码 | |
| 293 | + //****************** 查询状态 *************************** | |
| 294 | + if (swState.isChecked()) { | |
| 295 | + Command command = printer.getPortManager().getCommand(); | |
| 296 | + int status = printer.getPrinterState(command,2000); | |
| 297 | + if (status != 0) {//打印机处于不正常状态、则不发送打印 | |
| 298 | + Message msg = new Message(); | |
| 299 | + msg.what = 0x01; | |
| 300 | + msg.arg1 = status; | |
| 301 | + handler.sendMessage(msg); | |
| 302 | + return; | |
| 303 | + } | |
| 304 | + } | |
| 305 | + //*************************************************************** | |
| 306 | + File file = null; | |
| 307 | + try { | |
| 308 | + file= new File(context.getExternalCacheDir(), "WalmartFile.pdf"); | |
| 309 | + if (!file.exists()) { | |
| 310 | + // Since PdfRenderer cannot handle the compressed asset file directly, we copy it into | |
| 311 | + // the cache directory. | |
| 312 | + InputStream asset = context.getAssets().open("WalmartFile.pdf"); | |
| 313 | + FileOutputStream output = new FileOutputStream(file); | |
| 314 | + final byte[] buffer = new byte[1024]; | |
| 315 | + int size; | |
| 316 | + while ((size = asset.read(buffer)) != -1) { | |
| 317 | + output.write(buffer, 0, size); | |
| 318 | + } | |
| 319 | + asset.close(); | |
| 320 | + output.close(); | |
| 321 | + } | |
| 322 | + }catch (IOException e){ | |
| 323 | + tipsToast(getString(R.string.pdf_error)); | |
| 324 | + return; | |
| 325 | + } | |
| 326 | + boolean result= printer.getPortManager().writePDFToTsc(file,576,0,true,true,false,160); | |
| 327 | + if (result) { | |
| 328 | + tipsDialog(getString(R.string.send_success)); | |
| 329 | + }else { | |
| 330 | + tipsDialog(getString(R.string.send_fail)); | |
| 331 | + } | |
| 332 | + LogUtils.e("send result",result); | |
| 333 | + } catch (IOException e) { | |
| 334 | + tipsDialog(getString(R.string.disconnect)+"\n"+getString(R.string.print_fail)+e.getMessage()); | |
| 335 | + }catch (Exception e){ | |
| 336 | + tipsDialog(getString(R.string.print_fail)+e.getMessage()); | |
| 337 | + } | |
| 338 | + } | |
| 339 | + }); | |
| 340 | + } | |
| 341 | + | |
| 342 | + /** | |
| 343 | + * 检查标签打印机状态 | |
| 344 | + * @param view | |
| 345 | + */ | |
| 346 | + public void checkState(View view) { | |
| 347 | + ThreadPoolManager.getInstance().addTask(new Runnable() { | |
| 348 | + @Override | |
| 349 | + public void run() { | |
| 350 | + if (printer.getPortManager()==null){ | |
| 351 | + tipsToast(getString(R.string.conn_first)); | |
| 352 | + return; | |
| 353 | + } | |
| 354 | + try { | |
| 355 | + Command command=printer.getPortManager().getCommand(); | |
| 356 | + int status=printer.getPrinterState(command,2000); | |
| 357 | + Message msg=new Message(); | |
| 358 | + msg.what=0x01; | |
| 359 | + msg.arg1=status; | |
| 360 | + handler.sendMessage(msg); | |
| 361 | + } catch (IOException e) { | |
| 362 | + tipsDialog(getString(R.string.status_error)+e.getMessage()); | |
| 363 | + }catch (Exception e){ | |
| 364 | + tipsDialog(getString(R.string.status_error)+e.getMessage()); | |
| 365 | + } | |
| 366 | + } | |
| 367 | + }); | |
| 368 | + } | |
| 369 | + /** | |
| 370 | + * 提示弹框 | |
| 371 | + * @param message | |
| 372 | + */ | |
| 373 | + private void tipsToast(String message){ | |
| 374 | + Message msg =new Message(); | |
| 375 | + msg.what=0x00; | |
| 376 | + msg.obj=message; | |
| 377 | + handler.sendMessage(msg); | |
| 378 | + } | |
| 379 | + /** | |
| 380 | + * 提示弹框 | |
| 381 | + * @param message | |
| 382 | + */ | |
| 383 | + private void tipsDialog(String message){ | |
| 384 | + Message msg =new Message(); | |
| 385 | + msg.what=0x03; | |
| 386 | + msg.obj=message; | |
| 387 | + handler.sendMessage(msg); | |
| 388 | + } | |
| 389 | + | |
| 390 | + @Override | |
| 391 | + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { | |
| 392 | + if (resultCode== Activity.RESULT_OK){ | |
| 393 | + switch (requestCode){ | |
| 394 | + case 0x00://蓝牙返回mac地址 | |
| 395 | + String mac =data.getStringExtra(BlueToothDeviceActivity.EXTRA_DEVICE_ADDRESS); | |
| 396 | + Log.e(TAG, SDKUtils.bytesToHexString(mac.getBytes())); | |
| 397 | + PrinterDevices blueTooth=new PrinterDevices.Build() | |
| 398 | + .setContext(context) | |
| 399 | + .setConnMethod(ConnMethod.BLUETOOTH) | |
| 400 | + .setMacAddress(mac) | |
| 401 | + .setCommand(Command.TSC) | |
| 402 | + .setCallbackListener(this) | |
| 403 | + .build(); | |
| 404 | + printer.connect(blueTooth); | |
| 405 | + break; | |
| 406 | + case 0x01://usb返回USB名称 | |
| 407 | + String name =data.getStringExtra(UsbDeviceActivity.USB_NAME); | |
| 408 | + UsbDevice usbDevice = Utils.getUsbDeviceFromName(context, name); | |
| 409 | + PrinterDevices usb=new PrinterDevices.Build() | |
| 410 | + .setContext(context) | |
| 411 | + .setConnMethod(ConnMethod.USB) | |
| 412 | + .setUsbDevice(usbDevice) | |
| 413 | + .setCommand(Command.TSC) | |
| 414 | + .setCallbackListener(this) | |
| 415 | + .build(); | |
| 416 | + printer.connect(usb); | |
| 417 | + break; | |
| 418 | + case 0x02://WIFI返回ip | |
| 419 | + String ip =data.getStringExtra(WifiDeviceActivity.IP); | |
| 420 | + PrinterDevices wifi=new PrinterDevices.Build() | |
| 421 | + .setContext(context) | |
| 422 | + .setConnMethod(ConnMethod.WIFI) | |
| 423 | + .setIp(ip) | |
| 424 | + .setPort(9100)//打印唯一端口9100 | |
| 425 | + .setCommand(Command.TSC) | |
| 426 | + .setCallbackListener(this) | |
| 427 | + .build(); | |
| 428 | + printer.connect(wifi); | |
| 429 | + break; | |
| 430 | + case 0x03://串口返回路径、波特率 | |
| 431 | + int baudRate = data.getIntExtra(SerialPortDeviceActivity.SERIALPORT_BAUDRATE, 9600); | |
| 432 | + String path = data.getStringExtra(SerialPortDeviceActivity.SERIALPORT_PATH); | |
| 433 | + PrinterDevices serialPort=new PrinterDevices.Build() | |
| 434 | + .setContext(context) | |
| 435 | + .setConnMethod(ConnMethod.SERIALPORT) | |
| 436 | + .setSerialPort(path) | |
| 437 | + .setBaudrate(baudRate) | |
| 438 | + .setCommand(Command.TSC) | |
| 439 | + .setCallbackListener(this) | |
| 440 | + .build(); | |
| 441 | + printer.connect(serialPort); | |
| 442 | + break; | |
| 443 | + } | |
| 444 | + } | |
| 445 | + } | |
| 446 | + | |
| 447 | + @Override | |
| 448 | + public void onConnecting() {//连接打印机中 | |
| 449 | + tvState.setText(getString(R.string.conning)); | |
| 450 | + } | |
| 451 | + | |
| 452 | + @Override | |
| 453 | + public void onCheckCommand() {//查询打印机指令 | |
| 454 | + tvState.setText(getString(R.string.checking)); | |
| 455 | + } | |
| 456 | + | |
| 457 | + @Override | |
| 458 | + public void onSuccess(PrinterDevices devices) {//连接成功 | |
| 459 | + Toast.makeText(context,getString(R.string.conn_success),Toast.LENGTH_SHORT).show(); | |
| 460 | + tvState.setText(devices.toString()); | |
| 461 | + } | |
| 462 | + | |
| 463 | + @Override | |
| 464 | + public void onReceive(byte[] bytes) { | |
| 465 | + | |
| 466 | + } | |
| 467 | + | |
| 468 | + @Override | |
| 469 | + public void onFailure() {//连接失败 | |
| 470 | + Toast.makeText(context,getString(R.string.conn_fail),Toast.LENGTH_SHORT).show(); | |
| 471 | + handler.obtainMessage(0x02).sendToTarget(); | |
| 472 | + } | |
| 473 | + | |
| 474 | + @Override | |
| 475 | + public void onDisconnect() {//断开连接 | |
| 476 | + Toast.makeText(context,getString(R.string.disconnect),Toast.LENGTH_SHORT).show(); | |
| 477 | + handler.obtainMessage(0x02).sendToTarget(); | |
| 478 | + } | |
| 479 | + //申请权限返回 | |
| 480 | + @Override | |
| 481 | + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { | |
| 482 | + permissionUtils.handleRequestPermissionsResult(requestCode, permissions, grantResults); | |
| 483 | + } | |
| 484 | + @Override | |
| 485 | + protected void onDestroy() { | |
| 486 | + super.onDestroy(); | |
| 487 | + if (printer.getPortManager()!=null){ | |
| 488 | + printer.close(); | |
| 489 | + } | |
| 490 | + } | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/PermissionUtils.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import android.annotation.TargetApi; | |
| 4 | +import android.app.Activity; | |
| 5 | +import android.app.AlertDialog; | |
| 6 | +import android.app.Fragment; | |
| 7 | +import android.content.DialogInterface; | |
| 8 | +import android.content.pm.PackageManager; | |
| 9 | +import android.os.Build; | |
| 10 | + | |
| 11 | +import androidx.annotation.NonNull; | |
| 12 | +import androidx.annotation.Nullable; | |
| 13 | +import androidx.core.app.ActivityCompat; | |
| 14 | +import androidx.core.content.ContextCompat; | |
| 15 | + | |
| 16 | +import java.util.Arrays; | |
| 17 | +import java.util.List; | |
| 18 | + | |
| 19 | +/** | |
| 20 | + * @author deadline | |
| 21 | + * @time 2016-10-28 | |
| 22 | + * @usage android >=M 的权限申请统一处理 | |
| 23 | + * <p> | |
| 24 | + * notice: | |
| 25 | + * 很多手机对原生系统做了修改,比如小米4的6.0的shouldShowRequestPermissionRationale | |
| 26 | + * 就一直返回false,而且在申请权限时,如果用户选择了拒绝,则不会再弹出对话框了, 因此有了 | |
| 27 | + * void doAfterDenied(String... permission); | |
| 28 | + */ | |
| 29 | + | |
| 30 | +public class PermissionUtils { | |
| 31 | + | |
| 32 | + private static final int REQUEST_PERMISSION_CODE = 1000; | |
| 33 | + | |
| 34 | + private Object mContext; | |
| 35 | + | |
| 36 | + private PermissionListener mListener; | |
| 37 | + | |
| 38 | + private List<String> mPermissionList; | |
| 39 | + | |
| 40 | + public PermissionUtils(@NonNull Object object) { | |
| 41 | + checkCallingObjectSuitability(object); | |
| 42 | + this.mContext = object; | |
| 43 | + | |
| 44 | + } | |
| 45 | + | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * 权限授权申请 | |
| 49 | + * | |
| 50 | + * @param hintMessage 要申请的权限的提示 | |
| 51 | + * @param permissions 要申请的权限 | |
| 52 | + * @param listener 申请成功之后的callback | |
| 53 | + */ | |
| 54 | + public void requestPermissions(@NonNull CharSequence hintMessage, | |
| 55 | + @Nullable PermissionListener listener, | |
| 56 | + @NonNull final String... permissions) { | |
| 57 | + if (listener != null) { | |
| 58 | + mListener = listener; | |
| 59 | + } | |
| 60 | + mPermissionList = Arrays.asList(permissions); | |
| 61 | + //没全部权限 | |
| 62 | + if (!hasPermissions(mContext, permissions)) { | |
| 63 | + //需要向用户解释为什么申请这个权限 | |
| 64 | + boolean shouldShowRationale = false; | |
| 65 | + for (String perm : permissions) { | |
| 66 | + shouldShowRationale = | |
| 67 | + shouldShowRationale || shouldShowRequestPermissionRationale(mContext, perm); | |
| 68 | + } | |
| 69 | + | |
| 70 | + if (shouldShowRationale) { | |
| 71 | + showMessageOKCancel(hintMessage, new DialogInterface.OnClickListener() { | |
| 72 | + @Override | |
| 73 | + public void onClick(DialogInterface dialog, int which) { | |
| 74 | + executePermissionsRequest(mContext, permissions, | |
| 75 | + REQUEST_PERMISSION_CODE); | |
| 76 | + | |
| 77 | + } | |
| 78 | + }); | |
| 79 | + } else { | |
| 80 | + executePermissionsRequest(mContext, permissions, | |
| 81 | + REQUEST_PERMISSION_CODE); | |
| 82 | + } | |
| 83 | + } else if (mListener != null) { //有全部权限 | |
| 84 | + mListener.doAfterGrand(permissions); | |
| 85 | + } | |
| 86 | + } | |
| 87 | + | |
| 88 | + /** | |
| 89 | + * 处理onRequestPermissionsResult | |
| 90 | + * | |
| 91 | + * @param requestCode | |
| 92 | + * @param permissions | |
| 93 | + * @param grantResults | |
| 94 | + */ | |
| 95 | + public void handleRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { | |
| 96 | + switch (requestCode) { | |
| 97 | + case REQUEST_PERMISSION_CODE: | |
| 98 | + boolean allGranted = true; | |
| 99 | + for (int grant : grantResults) { | |
| 100 | + if (grant != PackageManager.PERMISSION_GRANTED) { | |
| 101 | + allGranted = false; | |
| 102 | + break; | |
| 103 | + } | |
| 104 | + } | |
| 105 | + | |
| 106 | + if (allGranted && mListener != null) { | |
| 107 | + | |
| 108 | + mListener.doAfterGrand((String[]) mPermissionList.toArray()); | |
| 109 | + | |
| 110 | + } else if (!allGranted && mListener != null) { | |
| 111 | + mListener.doAfterDenied((String[]) mPermissionList.toArray()); | |
| 112 | + } | |
| 113 | + break; | |
| 114 | + } | |
| 115 | + } | |
| 116 | + | |
| 117 | + /** | |
| 118 | + * 判断是否具有某权限 | |
| 119 | + * | |
| 120 | + * @param object | |
| 121 | + * @param perms | |
| 122 | + * @return | |
| 123 | + */ | |
| 124 | + public static boolean hasPermissions(@NonNull Object object, @NonNull String... perms) { | |
| 125 | + | |
| 126 | + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { | |
| 127 | + return true; | |
| 128 | + } | |
| 129 | + for (String perm : perms) { | |
| 130 | + boolean hasPerm = (ContextCompat.checkSelfPermission(getActivity(object), perm) == | |
| 131 | + PackageManager.PERMISSION_GRANTED); | |
| 132 | + if (!hasPerm) { | |
| 133 | + return false; | |
| 134 | + } | |
| 135 | + } | |
| 136 | + | |
| 137 | + return true; | |
| 138 | + } | |
| 139 | + | |
| 140 | + | |
| 141 | + /** | |
| 142 | + * 兼容fragment | |
| 143 | + * | |
| 144 | + * @param object | |
| 145 | + * @param perm | |
| 146 | + * @return | |
| 147 | + */ | |
| 148 | + @TargetApi(23) | |
| 149 | + private static boolean shouldShowRequestPermissionRationale(@NonNull Object object, @NonNull String perm) { | |
| 150 | + if (object instanceof Activity) { | |
| 151 | + return ActivityCompat.shouldShowRequestPermissionRationale((Activity) object, perm); | |
| 152 | + } else if (object instanceof Fragment) { | |
| 153 | + return ((Fragment) object).shouldShowRequestPermissionRationale(perm); | |
| 154 | + } else if (object instanceof Fragment) { | |
| 155 | + return ((Fragment) object).shouldShowRequestPermissionRationale(perm); | |
| 156 | + } else { | |
| 157 | + return false; | |
| 158 | + } | |
| 159 | + } | |
| 160 | + | |
| 161 | + /** | |
| 162 | + * 执行申请,兼容fragment | |
| 163 | + * | |
| 164 | + * @param object | |
| 165 | + * @param perms | |
| 166 | + * @param requestCode | |
| 167 | + */ | |
| 168 | + @TargetApi(23) | |
| 169 | + private void executePermissionsRequest(@NonNull Object object, @NonNull String[] perms, int requestCode) { | |
| 170 | + if (object instanceof Activity) { | |
| 171 | + ActivityCompat.requestPermissions((Activity) object, perms, requestCode); | |
| 172 | + } else if (object instanceof Fragment) { | |
| 173 | + ((Fragment) object).requestPermissions(perms, requestCode); | |
| 174 | + } else if (object instanceof Fragment) { | |
| 175 | + ((Fragment) object).requestPermissions(perms, requestCode); | |
| 176 | + } | |
| 177 | + } | |
| 178 | + | |
| 179 | + /** | |
| 180 | + * 检查传递Context是否合法 | |
| 181 | + * | |
| 182 | + * @param object | |
| 183 | + */ | |
| 184 | + private void checkCallingObjectSuitability(@Nullable Object object) { | |
| 185 | + if (object == null) { | |
| 186 | + throw new NullPointerException("Activity or Fragment should not be null"); | |
| 187 | + } | |
| 188 | + | |
| 189 | + boolean isActivity = object instanceof Activity; | |
| 190 | + boolean isSupportFragment = object instanceof Fragment; | |
| 191 | + boolean isAppFragment = object instanceof Fragment; | |
| 192 | + if (!(isSupportFragment || isActivity || (isAppFragment && isNeedRequest()))) { | |
| 193 | + if (isAppFragment) { | |
| 194 | + throw new IllegalArgumentException( | |
| 195 | + "Target SDK needs to be greater than 23 if caller is android.app.Fragment"); | |
| 196 | + } else { | |
| 197 | + throw new IllegalArgumentException("Caller must be an Activity or a Fragment."); | |
| 198 | + } | |
| 199 | + } | |
| 200 | + } | |
| 201 | + | |
| 202 | + | |
| 203 | + @TargetApi(11) | |
| 204 | + private static Activity getActivity(@NonNull Object object) { | |
| 205 | + if (object instanceof Activity) { | |
| 206 | + return ((Activity) object); | |
| 207 | + } else if (object instanceof Fragment) { | |
| 208 | + return ((Fragment) object).getActivity(); | |
| 209 | + } else if (object instanceof Fragment) { | |
| 210 | + return ((Fragment) object).getActivity(); | |
| 211 | + } else { | |
| 212 | + return null; | |
| 213 | + } | |
| 214 | + } | |
| 215 | + | |
| 216 | + public static boolean isNeedRequest() { | |
| 217 | + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; | |
| 218 | + } | |
| 219 | + | |
| 220 | + public void showMessageOKCancel(CharSequence message, DialogInterface.OnClickListener okListener) { | |
| 221 | + new AlertDialog.Builder(getActivity(mContext)) | |
| 222 | + .setMessage(message) | |
| 223 | + .setPositiveButton(getActivity(mContext).getString(android.R.string.ok), okListener) | |
| 224 | + .setNegativeButton(getActivity(mContext).getString(android.R.string.cancel), null) | |
| 225 | + .setCancelable(false) | |
| 226 | + .create() | |
| 227 | + .show(); | |
| 228 | + } | |
| 229 | + | |
| 230 | + public interface PermissionListener { | |
| 231 | + | |
| 232 | + void doAfterGrand(String... permission); | |
| 233 | + | |
| 234 | + void doAfterDenied(String... permission); | |
| 235 | + } | |
| 236 | +} | |
| 0 | 237 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/PrintContent.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.graphics.Bitmap; | |
| 5 | +import android.graphics.BitmapFactory; | |
| 6 | +import android.graphics.Canvas; | |
| 7 | +import android.graphics.Color; | |
| 8 | +import android.graphics.Matrix; | |
| 9 | +import android.view.View; | |
| 10 | +import android.widget.TableLayout; | |
| 11 | +import android.widget.TableRow; | |
| 12 | +import android.widget.TextView; | |
| 13 | +import com.gprinter.command.LabelCommand; | |
| 14 | +import com.gprinter.utils.GpUtils; | |
| 15 | +import com.gprinter.utils.PDFUtils; | |
| 16 | + | |
| 17 | +import java.io.BufferedReader; | |
| 18 | +import java.io.InputStreamReader; | |
| 19 | +import java.io.UnsupportedEncodingException; | |
| 20 | +import java.security.PublicKey; | |
| 21 | +import java.util.Vector; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * Copyright (C), 2012-2020, 珠海佳博科技股份有限公司 | |
| 25 | + * FileName: PrintConntent | |
| 26 | + * Author: Circle | |
| 27 | + * Date: 2020/7/20 10:04 | |
| 28 | + * Description: 打印内容 | |
| 29 | + */ | |
| 30 | +public class PrintContent { | |
| 31 | + /** | |
| 32 | + * 标签打印测试页 | |
| 33 | + * | |
| 34 | + * @return | |
| 35 | + */ | |
| 36 | + public static Vector<Byte> getLabel(Context context,int gap) { | |
| 37 | + LabelCommand tsc = new LabelCommand(); | |
| 38 | + // 设置标签尺寸宽高,按照实际尺寸设置 单位mm | |
| 39 | + tsc.addUserCommand("\r\n"); | |
| 40 | + tsc.addSize(58, 70); | |
| 41 | + // 设置标签间隙,按照实际尺寸设置,如果为无间隙纸则设置为0 单位mm | |
| 42 | + tsc.addGap(gap); | |
| 43 | + //设置纸张类型为黑标,发送BLINE 指令不能同时发送GAP指令 | |
| 44 | +// tsc.addBline(2); | |
| 45 | + // 设置打印方向 | |
| 46 | + tsc.addDirection(LabelCommand.DIRECTION.FORWARD, LabelCommand.MIRROR.NORMAL); | |
| 47 | + // 设置原点坐标 | |
| 48 | + tsc.addReference(0, 0); | |
| 49 | + //设置浓度 | |
| 50 | + tsc.addDensity(LabelCommand.DENSITY.DNESITY4); | |
| 51 | + // 撕纸模式开启 | |
| 52 | + tsc.addTear(LabelCommand.RESPONSE_MODE.ON); | |
| 53 | + // 清除打印缓冲区 | |
| 54 | + tsc.addCls(); | |
| 55 | + // 绘制简体中文 | |
| 56 | + tsc.addText(30, 20, LabelCommand.FONTTYPE.SIMPLIFIED_24_CHINESE, LabelCommand.ROTATION.ROTATION_0, LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1, | |
| 57 | + "欢迎使用Printer"); | |
| 58 | + //打印繁体 | |
| 59 | + tsc.addUnicodeText(30,50, LabelCommand.FONTTYPE.TRADITIONAL_CHINESE, LabelCommand.ROTATION.ROTATION_0, LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"BIG5碼繁體中文","BIG5"); | |
| 60 | + //打印韩文 | |
| 61 | + tsc.addUnicodeText(30,80, LabelCommand.FONTTYPE.KOREAN, LabelCommand.ROTATION.ROTATION_0, LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"Korean 지아보 하성","EUC_KR"); | |
| 62 | + //英数字 | |
| 63 | + tsc.addText(240,20, LabelCommand.FONTTYPE.FONT_1, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"1"); | |
| 64 | + tsc.addText(250,20, LabelCommand.FONTTYPE.FONT_2, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"2"); | |
| 65 | + tsc.addText(270,20, LabelCommand.FONTTYPE.FONT_3, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"3"); | |
| 66 | + tsc.addText(300,20, LabelCommand.FONTTYPE.FONT_4, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"4"); | |
| 67 | + tsc.addText(330,20, LabelCommand.FONTTYPE.FONT_5, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"5"); | |
| 68 | + tsc.addText(240,40, LabelCommand.FONTTYPE.FONT_6, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"6"); | |
| 69 | + tsc.addText(250,40, LabelCommand.FONTTYPE.FONT_7, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"7"); | |
| 70 | + tsc.addText(270,40, LabelCommand.FONTTYPE.FONT_8, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"8"); | |
| 71 | + tsc.addText(300,60, LabelCommand.FONTTYPE.FONT_9, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"9"); | |
| 72 | + tsc.addText(330,80, LabelCommand.FONTTYPE.FONT_10, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"10"); | |
| 73 | + Bitmap b = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_priter); | |
| 74 | + // 绘制图片 | |
| 75 | + tsc.drawImage(30, 100, 300, b); | |
| 76 | + Bitmap b2= BitmapFactory.decodeResource(context.getResources(), R.drawable.flower); | |
| 77 | + tsc.drawJPGImage(200,250,200,b2); | |
| 78 | + //绘制二维码 | |
| 79 | + tsc.addQRCode(30,250, LabelCommand.EEC.LEVEL_L, 5, LabelCommand.ROTATION.ROTATION_0, " www.smarnet.cc"); | |
| 80 | + // 绘制一维条码 | |
| 81 | + tsc.add1DBarcode(30, 380, LabelCommand.BARCODETYPE.CODE128, 80, LabelCommand.READABEL.EANBEL, LabelCommand.ROTATION.ROTATION_0, "12345678"); | |
| 82 | + // 打印标签 | |
| 83 | + tsc.addPrint(1, 1); | |
| 84 | + // 打印标签后 蜂鸣器响 | |
| 85 | + tsc.addSound(2, 100); | |
| 86 | + //开启钱箱 | |
| 87 | + tsc.addCashdrwer(LabelCommand.FOOT.F5, 255, 255); | |
| 88 | + Vector<Byte> datas = tsc.getCommand(); | |
| 89 | + // 发送数据 | |
| 90 | + return datas; | |
| 91 | + } | |
| 92 | + | |
| 93 | + | |
| 94 | + /** | |
| 95 | + * 获取图片 | |
| 96 | + * @param context | |
| 97 | + * @return | |
| 98 | + */ | |
| 99 | + public static Bitmap getBitmap(Context context) { | |
| 100 | + View v = View.inflate(context, R.layout.page, null); | |
| 101 | + TableLayout tableLayout = (TableLayout) v.findViewById(R.id.line); | |
| 102 | + TextView total = (TextView) v.findViewById(R.id.total); | |
| 103 | + TextView cashier = (TextView) v.findViewById(R.id.cashier); | |
| 104 | + tableLayout.addView(ctv(context, "红茶\n加热\n加糖", 3, 8)); | |
| 105 | + tableLayout.addView(ctv(context, "绿茶", 899, 109)); | |
| 106 | + tableLayout.addView(ctv(context, "咖啡", 4, 15)); | |
| 107 | + tableLayout.addView(ctv(context, "红茶", 3, 8)); | |
| 108 | + tableLayout.addView(ctv(context, "绿茶", 8, 10)); | |
| 109 | + tableLayout.addView(ctv(context, "咖啡", 4, 15)); | |
| 110 | + total.setText("998"); | |
| 111 | + cashier.setText("张三"); | |
| 112 | + final Bitmap bitmap = convertViewToBitmap(v); | |
| 113 | + return bitmap; | |
| 114 | + } | |
| 115 | + /** | |
| 116 | + * mxl转bitmap图片 | |
| 117 | + * @return | |
| 118 | + */ | |
| 119 | + public static Bitmap convertViewToBitmap(View view){ | |
| 120 | + view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); | |
| 121 | + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); | |
| 122 | + view.buildDrawingCache(); | |
| 123 | + Bitmap bitmap = view.getDrawingCache(); | |
| 124 | + return bitmap; | |
| 125 | + } | |
| 126 | + | |
| 127 | + public static TableRow ctv(Context context, String name, int k, int n){ | |
| 128 | + TableRow tb=new TableRow(context); | |
| 129 | + tb.setLayoutParams(new TableLayout.LayoutParams(TableLayout.LayoutParams.WRAP_CONTENT ,TableLayout.LayoutParams.WRAP_CONTENT)); | |
| 130 | + TextView tv1=new TextView(context); | |
| 131 | + tv1.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT)); | |
| 132 | + tv1.setText(name); | |
| 133 | + tv1.setTextColor(Color.BLACK); | |
| 134 | + tb.addView(tv1); | |
| 135 | + TextView tv2=new TextView(context); | |
| 136 | + tv2.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT)); | |
| 137 | + tv2.setText(k+""); | |
| 138 | + tv2.setTextColor(Color.BLACK); | |
| 139 | + tb.addView(tv2); | |
| 140 | + TextView tv3=new TextView(context); | |
| 141 | + tv3.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT)); | |
| 142 | + tv3.setText(n+""); | |
| 143 | + tv3.setTextColor(Color.BLACK); | |
| 144 | + tb.addView(tv3); | |
| 145 | + return tb; | |
| 146 | + } | |
| 147 | + /** | |
| 148 | + * 获取Assets文件 | |
| 149 | + * @param fileName | |
| 150 | + * @return | |
| 151 | + */ | |
| 152 | + public static String getFromAssets(Context context,String fileName) { | |
| 153 | + String result = ""; | |
| 154 | + try { | |
| 155 | + InputStreamReader inputReader = new InputStreamReader(context.getResources().getAssets().open(fileName)); | |
| 156 | + BufferedReader bufReader = new BufferedReader(inputReader); | |
| 157 | + String line = ""; | |
| 158 | + while ((line = bufReader.readLine()) != null) | |
| 159 | + result += line+"\r\n"; | |
| 160 | + return result; | |
| 161 | + } catch (Exception e) { | |
| 162 | + e.printStackTrace(); | |
| 163 | + } | |
| 164 | + return result; | |
| 165 | + } | |
| 166 | + public static Vector<Byte> getXmlBitmap(Context context){ | |
| 167 | + LabelCommand tsc = new LabelCommand(); | |
| 168 | + // 设置标签尺寸宽高,按照实际尺寸设置 单位mm | |
| 169 | + tsc.addUserCommand("\r\n"); | |
| 170 | + tsc.addSize(58, 100); | |
| 171 | + // 设置标签间隙,按照实际尺寸设置,如果为无间隙纸则设置为0 单位mm | |
| 172 | + tsc.addGap(0); | |
| 173 | + // 设置打印方向 | |
| 174 | + tsc.addDirection(LabelCommand.DIRECTION.FORWARD, LabelCommand.MIRROR.NORMAL); | |
| 175 | + // 设置原点坐标 | |
| 176 | + tsc.addReference(0, 0); | |
| 177 | + //设置浓度 | |
| 178 | + tsc.addDensity(LabelCommand.DENSITY.DNESITY4); | |
| 179 | + // 撕纸模式开启 | |
| 180 | + tsc.addTear(LabelCommand.RESPONSE_MODE.ON); | |
| 181 | + // 清除打印缓冲区 | |
| 182 | + tsc.addCls(); | |
| 183 | + Bitmap bitmap=getBitmap(context); | |
| 184 | + // 绘制图片 | |
| 185 | + /** | |
| 186 | + * x:打印起始横坐标 | |
| 187 | + * y:打印起始纵坐标 | |
| 188 | + * mWidth:打印宽度以dot为单位 | |
| 189 | + * nbitmap:源图 | |
| 190 | + */ | |
| 191 | + tsc.drawXmlImage(10,10,bitmap.getWidth(),bitmap); | |
| 192 | + // 打印标签 | |
| 193 | + tsc.addPrint(1, 1); | |
| 194 | + return tsc.getCommand(); | |
| 195 | + } | |
| 196 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/PrintContent2.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import static com.gprinter.bean.BarCodeType.BARCODE_CODE128; | |
| 4 | + | |
| 5 | +import android.content.Context; | |
| 6 | +import android.graphics.Bitmap; | |
| 7 | +import android.graphics.BitmapFactory; | |
| 8 | +import android.graphics.Color; | |
| 9 | +import android.view.View; | |
| 10 | +import android.widget.TableLayout; | |
| 11 | +import android.widget.TableRow; | |
| 12 | +import android.widget.TextView; | |
| 13 | + | |
| 14 | +import com.gprinter.command.EscCommand; | |
| 15 | +import com.gprinter.command.EscForDotCommand; | |
| 16 | +import com.gprinter.command.LabelCommand; | |
| 17 | +import com.gprinter.utils.Menu58Utils; | |
| 18 | +import com.gprinter.utils.Menu80Utils; | |
| 19 | + | |
| 20 | +import java.util.Vector; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * Copyright (C), 2012-2020, 打印机有限公司 | |
| 24 | + * FileName: PrintConntent | |
| 25 | + * Author: Circle | |
| 26 | + * Date: 2020/7/20 10:04 | |
| 27 | + * Description: 打印内容 | |
| 28 | + */ | |
| 29 | +public class PrintContent2 { | |
| 30 | + /** | |
| 31 | + * 小票案例 | |
| 32 | + * @param context | |
| 33 | + * | |
| 34 | + * @return | |
| 35 | + */ | |
| 36 | + public static Vector<Byte> getReceiptChinese(Context context,int width) { | |
| 37 | + EscCommand esc = new EscCommand(); | |
| 38 | + //初始化打印机 | |
| 39 | + esc.addInitializePrinter(); | |
| 40 | + // 设置打印居中 | |
| 41 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER); | |
| 42 | + //字体放大两倍 | |
| 43 | + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_2, EscCommand.HEIGHT_ZOOM.MUL_2); | |
| 44 | + // 打印文字 | |
| 45 | + esc.addText("票据测试\n"); | |
| 46 | + | |
| 47 | + //字体正常 | |
| 48 | + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_1, EscCommand.HEIGHT_ZOOM.MUL_1); | |
| 49 | + //打印并换行 | |
| 50 | + esc.addPrintAndLineFeed(); | |
| 51 | + // 设置打印左对齐 | |
| 52 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT); | |
| 53 | + // 打印文字 | |
| 54 | + esc.addText("打印文字测试:\n"); | |
| 55 | + | |
| 56 | +// esc.addSetKanjiUnderLine(EscCommand.UNDERLINE_MODE.UNDERLINE_2DOT);//汉字下划线 | |
| 57 | + esc.addTurnUnderlineModeOnOrOff(EscCommand.UNDERLINE_MODE.UNDERLINE_2DOT);//非汉字下划线 | |
| 58 | + esc.addText("125545AA测试下划线\n"); | |
| 59 | + esc.addTurnUnderlineModeOnOrOff(EscCommand.UNDERLINE_MODE.OFF);//非汉字取消下划线 | |
| 60 | + | |
| 61 | + // 打印文字 | |
| 62 | + esc.addText("欢迎使用打印机!\n"); | |
| 63 | + esc.addPrintAndLineFeed(); | |
| 64 | + //对齐方式 | |
| 65 | + esc.addText("打印对齐方式测试:\n"); | |
| 66 | + //设置居左 | |
| 67 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT); | |
| 68 | + esc.addText("居左"); | |
| 69 | + esc.addPrintAndLineFeed(); | |
| 70 | + //设置居中 | |
| 71 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER); | |
| 72 | + esc.addText("居中"); | |
| 73 | + esc.addPrintAndLineFeed(); | |
| 74 | + //设置居右 | |
| 75 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.RIGHT); | |
| 76 | + esc.addText("居右"); | |
| 77 | + esc.addPrintAndLineFeed(); | |
| 78 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT); | |
| 79 | + // 打印图片 | |
| 80 | + esc.addText("打印Bitmap图测试:\n"); | |
| 81 | + Bitmap b = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_priter);//二维码图片,图片类型bitmap | |
| 82 | + // 打印图片,58打印机图片宽度最大为384dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8 | |
| 83 | + //打印图片,80打印机图片宽度最大为576dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8 | |
| 84 | + esc.drawImage(b, width); | |
| 85 | + esc.addPrintAndLineFeed(); | |
| 86 | + //打印条码 | |
| 87 | + esc.addText("打印条码测试:\n"); | |
| 88 | + // 设置条码可识别字符位置在条码下方 | |
| 89 | + esc.addSelectPrintingPositionForHRICharacters(EscCommand.HRI_POSITION.BELOW); | |
| 90 | + // 设置条码高度为60点 | |
| 91 | + esc.addSetBarcodeHeight((byte) 60); | |
| 92 | + // 设置条码单元宽度为1 | |
| 93 | + esc.addSetBarcodeWidth((byte) 1); | |
| 94 | + // 打印Code128码内容 | |
| 95 | + esc.addCODE128(esc.genCodeB("barcode128")); | |
| 96 | + esc.addPrintAndLineFeed(); | |
| 97 | + // 打印二维码 | |
| 98 | + esc.addText("打印二维码测试:\n"); | |
| 99 | + // 设置纠错等级 | |
| 100 | + esc.addSelectErrorCorrectionLevelForQRCode((byte) 0x31); | |
| 101 | + // 设置qrcode模块大小 | |
| 102 | + esc.addSelectSizeOfModuleForQRCode((byte) 4); | |
| 103 | + // 设置qrcode内容 | |
| 104 | + esc.addStoreQRCodeData("www.baidu.com"); | |
| 105 | + // 打印QRCode | |
| 106 | + esc.addPrintQRCode(); | |
| 107 | + esc.addPrintAndLineFeed(); | |
| 108 | + | |
| 109 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER); | |
| 110 | + //打印文字 | |
| 111 | + esc.addSelectCharacterFont(EscCommand.FONT.FONTB); | |
| 112 | + esc.addText("测试完成!\r\n"); | |
| 113 | + // 设置打印居左 | |
| 114 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT); | |
| 115 | + esc.addPrintAndLineFeed(); | |
| 116 | + esc.addPrintAndFeedLines((byte) 4); | |
| 117 | + //切纸(带切刀打印机才可用) | |
| 118 | + esc.addCutPaper(); | |
| 119 | + // 开钱箱 | |
| 120 | + esc.addGeneratePlus(LabelCommand.FOOT.F2, (byte) 255, (byte) 255); | |
| 121 | + esc.addInitializePrinter(); | |
| 122 | + //返回指令集 | |
| 123 | + return esc.getCommand(); | |
| 124 | + } | |
| 125 | + | |
| 126 | + | |
| 127 | + public static Vector<Byte> getImgRes(Context context,int res) { | |
| 128 | + EscCommand esc = new EscCommand(); | |
| 129 | + //初始化打印机 | |
| 130 | + esc.addInitializePrinter(); | |
| 131 | + // 设置打印居中 | |
| 132 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER); | |
| 133 | + Bitmap b = BitmapFactory.decodeResource(context.getResources(), res); | |
| 134 | + // 打印图片,图片宽度为384dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8 | |
| 135 | + esc.drawJpgImage(b, 200); | |
| 136 | + esc.addText("\n\n\n\n"); | |
| 137 | + esc.addPrintAndLineFeed(); | |
| 138 | + //切纸(带切刀打印机才可用) | |
| 139 | + esc.addCutPaper(); | |
| 140 | + // 开钱箱 | |
| 141 | + esc.addGeneratePlus(LabelCommand.FOOT.F2, (byte) 255, (byte) 255); | |
| 142 | + esc.addInitializePrinter(); | |
| 143 | + return esc.getCommand(); | |
| 144 | + } | |
| 145 | + | |
| 146 | + | |
| 147 | + /** | |
| 148 | + * 针式打印指令 | |
| 149 | + * @param qrCode | |
| 150 | + * @param barCode | |
| 151 | + * @return | |
| 152 | + */ | |
| 153 | + public static Vector<Byte> getDotPrintCommand(String qrCode,String barCode) { | |
| 154 | + EscForDotCommand esc = new EscForDotCommand(); | |
| 155 | + //初始化打印机 | |
| 156 | + esc.addInitializePrinter(); | |
| 157 | + // 设置打印居中 | |
| 158 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER); | |
| 159 | + esc.addText("二维码打印"); | |
| 160 | + esc.addText("\n\n"); | |
| 161 | + esc.addQrcode(100,100,qrCode); | |
| 162 | + // 打印图片,图片宽度为384dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8 | |
| 163 | + esc.addText("\n\n"); | |
| 164 | + esc.addText("条形码打印"); | |
| 165 | + esc.addText("\n\n"); | |
| 166 | + esc.addBarcode(BARCODE_CODE128,200,50,true,12,barCode); | |
| 167 | + esc.addText("\n\n\n\n"); | |
| 168 | + esc.addPrintAndLineFeed(); | |
| 169 | + //切纸(带切刀打印机才可用) | |
| 170 | + esc.addCutPaper(); | |
| 171 | + // 开钱箱 | |
| 172 | + esc.addGeneratePlus(LabelCommand.FOOT.F2, (byte) 255, (byte) 255); | |
| 173 | + esc.addInitializePrinter(); | |
| 174 | + return esc.getCommand(); | |
| 175 | + } | |
| 176 | + | |
| 177 | + | |
| 178 | + /** | |
| 179 | + * 菜单样例 | |
| 180 | + * @param context | |
| 181 | + * @return | |
| 182 | + */ | |
| 183 | + public static Vector<Byte> get58Menu(Context context) { | |
| 184 | + EscCommand esc = new EscCommand(); | |
| 185 | + //初始化打印机 | |
| 186 | + esc.addInitializePrinter(); | |
| 187 | + // 设置打印居中 | |
| 188 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER); | |
| 189 | + Bitmap b = BitmapFactory.decodeResource(context.getResources(), R.drawable.flower);//二维码图片,图片类型bitmap | |
| 190 | + // 打印图片,图片宽度为384dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8 | |
| 191 | + esc.drawJpgImage(b, 200); | |
| 192 | + // 设置为倍高倍宽 | |
| 193 | + //字体放大两倍 | |
| 194 | + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_2, EscCommand.HEIGHT_ZOOM.MUL_2); | |
| 195 | + // 打印文字 | |
| 196 | + esc.addText("爱情餐厅\n\n"); | |
| 197 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT); | |
| 198 | + esc.addText("520号桌\n\n"); | |
| 199 | + //字体正常 | |
| 200 | + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_1, EscCommand.HEIGHT_ZOOM.MUL_1); | |
| 201 | + esc.addText("点菜时间 2020-05-20 5:20\n"); | |
| 202 | + esc.addText("上菜时间 2020-05-20 13:14\n"); | |
| 203 | + esc.addText("人数:2人 点菜员:新疆包工头\n"); | |
| 204 | + esc.addText("--------------------------------\n"); | |
| 205 | + //开启加粗 | |
| 206 | + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.ON); | |
| 207 | + esc.addText(Menu58Utils.printThreeData("菜名", "数量", "金额")); | |
| 208 | + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.OFF); | |
| 209 | + esc.addText(Menu58Utils.printThreeData("北京烤鸭", "1", "99.99")); | |
| 210 | + esc.addText(Menu58Utils.printThreeData("四川麻婆豆腐", "1", "39.99")); | |
| 211 | + esc.addText(Menu58Utils.printThreeData("西湖醋鱼", "1", "59.99")); | |
| 212 | + esc.addText(Menu58Utils.printThreeData("飞龙汤", "1", "66.66")); | |
| 213 | + esc.addText(Menu58Utils.printThreeData("无为熏鸭", "1", "88.88")); | |
| 214 | + esc.addText(Menu58Utils.printThreeData("东坡肉", "1", "39.99")); | |
| 215 | + esc.addText(Menu58Utils.printThreeData("辣子鸡", "1", "66.66")); | |
| 216 | + esc.addText(Menu58Utils.printThreeData("腊味合蒸", "1", "108.00")); | |
| 217 | + esc.addText(Menu58Utils.printThreeData("东安子鸡", "1", "119.00")); | |
| 218 | + esc.addText(Menu58Utils.printThreeData("清蒸武昌鱼", "1", "88.88")); | |
| 219 | + esc.addText(Menu58Utils.printThreeData("再来两瓶82年的快乐肥宅水(去冰)", "1", "9.99")); | |
| 220 | + esc.addText(Menu58Utils.printThreeData("老干妈拌饭(加辣、加香菜)", "1", "6.66")); | |
| 221 | + esc.addText("--------------------------------\n\n"); | |
| 222 | + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.ON); | |
| 223 | + esc.addText(Menu58Utils.printTwoData("合计:", "1314.00")); | |
| 224 | + esc.addText(Menu58Utils.printTwoData("抹零:", "14.00")); | |
| 225 | + esc.addText(Menu58Utils.printTwoData("应收:", "1300.00")); | |
| 226 | + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.OFF); | |
| 227 | + esc.addText("--------------------------------\n"); | |
| 228 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.RIGHT); | |
| 229 | + esc.addText("收银员:广东包租公\n"); | |
| 230 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT); | |
| 231 | + esc.addText("宣言:我点个鸡蛋都是爱你的形状哦"); | |
| 232 | + esc.addPrintAndLineFeed(); | |
| 233 | + esc.addPrintAndLineFeed(); | |
| 234 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER); | |
| 235 | + // 设置纠错等级 | |
| 236 | + esc.addSelectErrorCorrectionLevelForQRCode((byte) 0x31); | |
| 237 | + // 设置qrcode模块大小 | |
| 238 | + esc.addSelectSizeOfModuleForQRCode((byte) 4); | |
| 239 | + // 设置qrcode内容 | |
| 240 | + esc.addStoreQRCodeData("https://www.baidu.com"); | |
| 241 | + // 打印QRCode | |
| 242 | + esc.addPrintQRCode(); | |
| 243 | + esc.addText("\n(扫二维码送手机)\n"); | |
| 244 | + esc.addText("\n\n\n\n"); | |
| 245 | + esc.addPrintAndLineFeed(); | |
| 246 | + //切纸(带切刀打印机才可用) | |
| 247 | + esc.addCutPaper(); | |
| 248 | + // 开钱箱 | |
| 249 | + esc.addGeneratePlus(LabelCommand.FOOT.F2, (byte) 255, (byte) 255); | |
| 250 | + esc.addInitializePrinter(); | |
| 251 | + //返回指令集 | |
| 252 | + return esc.getCommand(); | |
| 253 | + } | |
| 254 | + /** | |
| 255 | + * 菜单样例 | |
| 256 | + * @param context | |
| 257 | + * @return | |
| 258 | + */ | |
| 259 | + public static Vector<Byte> get80Menu(Context context) { | |
| 260 | + EscCommand esc = new EscCommand(); | |
| 261 | + //初始化打印机 | |
| 262 | + esc.addInitializePrinter(); | |
| 263 | + // 设置打印居中 | |
| 264 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER); | |
| 265 | + Bitmap b = BitmapFactory.decodeResource(context.getResources(), R.drawable.flower);//二维码图片,图片类型bitmap | |
| 266 | + // 打印图片,图片宽度为384dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8 | |
| 267 | + esc.drawJpgImage(b, 200); | |
| 268 | + // 设置为倍高倍宽 | |
| 269 | + //字体放大两倍 | |
| 270 | + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_2, EscCommand.HEIGHT_ZOOM.MUL_2); | |
| 271 | + // 打印文字 | |
| 272 | + esc.addText("爱情餐厅\n\n"); | |
| 273 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT); | |
| 274 | + esc.addText("520号桌\n\n"); | |
| 275 | + //字体正常 | |
| 276 | + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_1, EscCommand.HEIGHT_ZOOM.MUL_1); | |
| 277 | + esc.addText("点菜时间 2020-05-20 5:20\n"); | |
| 278 | + esc.addText("上菜时间 2020-05-20 13:14\n"); | |
| 279 | + esc.addText("人数:2人 点菜员:新疆包工头\n"); | |
| 280 | + esc.addText("------------------三行菜单案例------------------\n"); | |
| 281 | + esc.addText(Menu80Utils.printThreeData("菜名", "数量", "金额")); | |
| 282 | + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.OFF); | |
| 283 | + esc.addText(Menu80Utils.printThreeData("北京烤鸭", "1", "99.99")); | |
| 284 | + esc.addText(Menu80Utils.printThreeData("四川麻婆豆腐", "1", "39.99")); | |
| 285 | + esc.addText(Menu80Utils.printThreeData("西湖醋鱼", "1", "59.99")); | |
| 286 | + esc.addText(Menu80Utils.printThreeData("飞龙汤", "1", "66.66")); | |
| 287 | + esc.addText(Menu80Utils.printThreeData("无为熏鸭", "1", "88.88")); | |
| 288 | + esc.addText(Menu80Utils.printThreeData("东坡肉", "1", "39.99")); | |
| 289 | + esc.addText("------------------四行菜单案例------------------\n"); | |
| 290 | + //开启加粗 | |
| 291 | + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.ON); | |
| 292 | + esc.addText(Menu80Utils.printFourData("菜名", "单价","数量", "金额")); | |
| 293 | + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.OFF); | |
| 294 | + esc.addText(Menu80Utils.printFourData("北京烤鸭", "99.99","1", "99.99")); | |
| 295 | + esc.addText(Menu80Utils.printFourData("四川麻婆豆腐", "39.99","1", "39.99")); | |
| 296 | + esc.addText(Menu80Utils.printFourData("西湖醋鱼", "59.99","1", "59.99")); | |
| 297 | + esc.addText(Menu80Utils.printFourData("飞龙汤", "66.66","1", "66.66")); | |
| 298 | + esc.addText(Menu80Utils.printFourData("无为熏鸭", "88.88","1", "88.88")); | |
| 299 | + esc.addText(Menu80Utils.printFourData("东坡肉", "39.99","1", "39.99")); | |
| 300 | + esc.addText(Menu80Utils.printFourData("辣子鸡", "66.66","1", "66.66")); | |
| 301 | + esc.addText(Menu80Utils.printFourData("腊味合蒸", "108.00","1", "108.00")); | |
| 302 | + esc.addText(Menu80Utils.printFourData("东安子鸡", "119.00","1", "119.00")); | |
| 303 | + esc.addText(Menu80Utils.printFourData("清蒸武昌鱼", "88.88","1", "88.88")); | |
| 304 | + esc.addText(Menu80Utils.printFourData("再来两瓶82年的快乐肥宅水(去冰)", "9.00","11", "99.00")); | |
| 305 | + esc.addText(Menu80Utils.printFourData("老干妈拌饭", "6.66","1", "6.66")); | |
| 306 | + esc.addText("------------------------------------------------\n"); | |
| 307 | + esc.addText(Menu80Utils.printTwoData("合计:", "1314.00")); | |
| 308 | + esc.addText(Menu80Utils.printTwoData("抹零:", "14.00")); | |
| 309 | + esc.addText(Menu80Utils.printTwoData("应收:", "1300.00")); | |
| 310 | + esc.addText("------------------------------------------------\n"); | |
| 311 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.RIGHT); | |
| 312 | + esc.addText("收银员:广东包租公\n"); | |
| 313 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT); | |
| 314 | + esc.addText("宣言:我点个鸡蛋都是爱你的形状哦\n"); | |
| 315 | + | |
| 316 | + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER); | |
| 317 | + // 设置纠错等级 | |
| 318 | + esc.addSelectErrorCorrectionLevelForQRCode((byte) 0x31); | |
| 319 | + // 设置qrcode模块大小 | |
| 320 | + esc.addSelectSizeOfModuleForQRCode((byte) 4); | |
| 321 | + // 设置qrcode内容 | |
| 322 | + esc.addStoreQRCodeData("www.baidu.com"); | |
| 323 | + // 打印QRCode | |
| 324 | + esc.addPrintQRCode(); | |
| 325 | + esc.addText("\n(扫二维码送手机)\n"); | |
| 326 | + esc.addText("\n\n\n\n\n"); | |
| 327 | + esc.addPrintAndLineFeed(); | |
| 328 | + //切纸(带切刀打印机才可用) | |
| 329 | + esc.addCutPaper(); | |
| 330 | + // 开钱箱 | |
| 331 | + esc.addGeneratePlus(LabelCommand.FOOT.F2, (byte) 255, (byte) 255); | |
| 332 | + esc.addInitializePrinter(); | |
| 333 | + //返回指令集 | |
| 334 | + return esc.getCommand(); | |
| 335 | + } | |
| 336 | + | |
| 337 | + /** | |
| 338 | + * 打印自检页 | |
| 339 | + * @return | |
| 340 | + */ | |
| 341 | + public static Vector<Byte> getSelfTest() { | |
| 342 | + EscCommand esc = new EscCommand(); | |
| 343 | + byte[] escSelfTestCommand = {0x1f, 0x1b, 0x1f, (byte) 0x93, 0x10, 0x11, 0x12, 0x15, 0x16, 0x17, 0x10, 0x00}; | |
| 344 | + esc.addUserCommand(escSelfTestCommand); | |
| 345 | + return esc.getCommand(); | |
| 346 | + } | |
| 347 | + /** | |
| 348 | + * 获取图片 | |
| 349 | + * @param context | |
| 350 | + * @return | |
| 351 | + */ | |
| 352 | + public static Bitmap getBitmap(Context context) { | |
| 353 | + View v = View.inflate(context, R.layout.page, null); | |
| 354 | + TableLayout tableLayout = (TableLayout) v.findViewById(R.id.line); | |
| 355 | + TextView total = (TextView) v.findViewById(R.id.total); | |
| 356 | + TextView cashier = (TextView) v.findViewById(R.id.cashier); | |
| 357 | + tableLayout.addView(ctv(context, "红茶\n加热\n加糖", 3, 8)); | |
| 358 | + tableLayout.addView(ctv(context, "绿茶", 899, 109)); | |
| 359 | + tableLayout.addView(ctv(context, "咖啡", 4, 15)); | |
| 360 | + tableLayout.addView(ctv(context, "红茶", 3, 8)); | |
| 361 | + tableLayout.addView(ctv(context, "绿茶", 8, 10)); | |
| 362 | + tableLayout.addView(ctv(context, "咖啡", 4, 15)); | |
| 363 | + tableLayout.addView(ctv(context, "红茶", 3, 8)); | |
| 364 | + tableLayout.addView(ctv(context, "绿茶", 8, 10)); | |
| 365 | + tableLayout.addView(ctv(context, "咖啡", 4, 15)); | |
| 366 | + tableLayout.addView(ctv(context, "红茶", 3, 8)); | |
| 367 | + tableLayout.addView(ctv(context, "绿茶", 8, 10)); | |
| 368 | + tableLayout.addView(ctv(context, "咖啡", 4, 15)); | |
| 369 | + tableLayout.addView(ctv(context, "红茶", 3, 8)); | |
| 370 | + tableLayout.addView(ctv(context, "绿茶", 8, 10)); | |
| 371 | + tableLayout.addView(ctv(context, "咖啡", 4, 15)); | |
| 372 | + total.setText("998"); | |
| 373 | + cashier.setText("张三"); | |
| 374 | + final Bitmap bitmap = convertViewToBitmap(v); | |
| 375 | + return bitmap; | |
| 376 | + } | |
| 377 | + /** | |
| 378 | + * mxl转bitmap图片 | |
| 379 | + * @param view | |
| 380 | + * @return | |
| 381 | + */ | |
| 382 | + public static Bitmap convertViewToBitmap(View view){ | |
| 383 | + view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); | |
| 384 | + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); | |
| 385 | + view.buildDrawingCache(); | |
| 386 | + Bitmap bitmap = view.getDrawingCache(); | |
| 387 | + return bitmap; | |
| 388 | + } | |
| 389 | + | |
| 390 | + public static TableRow ctv(Context context, String name, int k, int n){ | |
| 391 | + TableRow tb=new TableRow(context); | |
| 392 | + tb.setLayoutParams(new TableLayout.LayoutParams(TableLayout.LayoutParams.WRAP_CONTENT ,TableLayout.LayoutParams.WRAP_CONTENT)); | |
| 393 | + TextView tv1=new TextView(context); | |
| 394 | + tv1.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT)); | |
| 395 | + tv1.setText(name); | |
| 396 | + tv1.setTextColor(Color.BLACK); | |
| 397 | + tv1.setTextSize(30); | |
| 398 | + tb.addView(tv1); | |
| 399 | + TextView tv2=new TextView(context); | |
| 400 | + tv2.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT)); | |
| 401 | + tv2.setText(k+""); | |
| 402 | + tv2.setTextColor(Color.BLACK); | |
| 403 | + tv2.setTextSize(30); | |
| 404 | + tb.addView(tv2); | |
| 405 | + TextView tv3=new TextView(context); | |
| 406 | + tv3.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT)); | |
| 407 | + tv3.setText(n+""); | |
| 408 | + tv3.setTextColor(Color.BLACK); | |
| 409 | + tv3.setTextSize(30); | |
| 410 | + tb.addView(tv3); | |
| 411 | + return tb; | |
| 412 | + } | |
| 413 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/Printer.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import com.gprinter.bean.PrinterDevices; | |
| 4 | +import com.gprinter.io.BluetoothPort; | |
| 5 | +import com.gprinter.io.EthernetPort; | |
| 6 | +import com.gprinter.io.PortManager; | |
| 7 | +import com.gprinter.io.SerialPort; | |
| 8 | +import com.gprinter.io.UsbPort; | |
| 9 | +import com.gprinter.utils.Command; | |
| 10 | + | |
| 11 | +import java.io.IOException; | |
| 12 | +import java.util.Vector; | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * Copyright (C), 2012-2019, 打印机有限公司 | |
| 16 | + * FileName: Printer | |
| 17 | + * Author: Circle | |
| 18 | + * Date: 2019/12/25 19:46 | |
| 19 | + * Description: 打印机使用单例 | |
| 20 | + */ | |
| 21 | +public class Printer { | |
| 22 | + public static Printer printer=null; | |
| 23 | + public static PortManager portManager=null; | |
| 24 | + public final PrinterDevices devices=null; | |
| 25 | + public Printer(){ | |
| 26 | + } | |
| 27 | + /** | |
| 28 | + * 单例 | |
| 29 | + * @return | |
| 30 | + */ | |
| 31 | + public static Printer getInstance(){ | |
| 32 | + if (printer==null){ | |
| 33 | + printer=new Printer(); | |
| 34 | + } | |
| 35 | + return printer; | |
| 36 | + } | |
| 37 | + | |
| 38 | + /** | |
| 39 | + * 获取打印机管理类 | |
| 40 | + * @return | |
| 41 | + */ | |
| 42 | + public static PortManager getPortManager(){ | |
| 43 | + return portManager; | |
| 44 | + } | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * 获取连接状态 | |
| 48 | + * @return | |
| 49 | + */ | |
| 50 | + public static boolean getConnectState(){ | |
| 51 | + return portManager.getConnectStatus(); | |
| 52 | + } | |
| 53 | + | |
| 54 | + /** | |
| 55 | + * 连接 | |
| 56 | + * @param devices | |
| 57 | + */ | |
| 58 | + public static void connect(final PrinterDevices devices){ | |
| 59 | + ThreadPoolManager.getInstance().addTask(new Runnable() { | |
| 60 | + @Override | |
| 61 | + public void run() { | |
| 62 | + if (portManager!=null) {//先close上次连接 | |
| 63 | + portManager.closePort(); | |
| 64 | + try { | |
| 65 | + Thread.sleep(2000); | |
| 66 | + } catch (InterruptedException e) { | |
| 67 | + } | |
| 68 | + } | |
| 69 | + if (devices!=null) { | |
| 70 | + switch (devices.getConnMethod()) { | |
| 71 | + case BLUETOOTH://蓝牙 | |
| 72 | + portManager = new BluetoothPort(devices); | |
| 73 | + portManager.openPort(); | |
| 74 | + break; | |
| 75 | + case USB://USB | |
| 76 | + portManager = new UsbPort(devices); | |
| 77 | + portManager.openPort(); | |
| 78 | + break; | |
| 79 | + case WIFI://WIFI | |
| 80 | + portManager = new EthernetPort(devices); | |
| 81 | + portManager.openPort(); | |
| 82 | + break; | |
| 83 | + case SERIALPORT://串口 | |
| 84 | + portManager=new SerialPort(devices); | |
| 85 | + portManager.openPort(); | |
| 86 | + break; | |
| 87 | + default: | |
| 88 | + break; | |
| 89 | + } | |
| 90 | + } | |
| 91 | + | |
| 92 | + } | |
| 93 | + }); | |
| 94 | + } | |
| 95 | + /** | |
| 96 | + * 发送数据到打印机 字节数据 | |
| 97 | + * @param vector | |
| 98 | + * @return true发送成功 false 发送失败 | |
| 99 | + * 打印机连接异常或断开发送时会抛异常,可以捕获异常进行处理 | |
| 100 | + */ | |
| 101 | + public static boolean sendDataToPrinter(byte [] vector) throws IOException { | |
| 102 | + if (portManager==null){ | |
| 103 | + return false; | |
| 104 | + } | |
| 105 | + return portManager.writeDataImmediately(vector); | |
| 106 | + } | |
| 107 | + | |
| 108 | + /** | |
| 109 | + * 获取打印机状态 | |
| 110 | + * @param printerCommand 打印机命令 ESC为小票,TSC为标签 ,CPCL为面单 | |
| 111 | + * @return 返回值常见文档说明 | |
| 112 | + * @throws IOException | |
| 113 | + */ | |
| 114 | + public static int getPrinterState(Command printerCommand, long delayMillis)throws IOException { | |
| 115 | + return portManager.getPrinterStatus(printerCommand); | |
| 116 | + } | |
| 117 | + | |
| 118 | + /** | |
| 119 | + * 获取打印机电量 | |
| 120 | + * @return | |
| 121 | + * @throws IOException | |
| 122 | + */ | |
| 123 | + public static int getPower() throws IOException { | |
| 124 | + return portManager.getPower(); | |
| 125 | + } | |
| 126 | + /** | |
| 127 | + * 获取打印机指令 | |
| 128 | + * @return | |
| 129 | + */ | |
| 130 | + public static Command getPrinterCommand(){ | |
| 131 | + return portManager.getCommand(); | |
| 132 | + } | |
| 133 | + | |
| 134 | + /** | |
| 135 | + * 设置使用指令 | |
| 136 | + * @param printerCommand | |
| 137 | + */ | |
| 138 | + public static void setPrinterCommand(Command printerCommand){ | |
| 139 | + if (portManager==null){ | |
| 140 | + return; | |
| 141 | + } | |
| 142 | + portManager.setCommand(printerCommand); | |
| 143 | + } | |
| 144 | + /** | |
| 145 | + * 发送数据到打印机 指令集合内容 | |
| 146 | + * @param vector | |
| 147 | + * @return true发送成功 false 发送失败 | |
| 148 | + * 打印机连接异常或断开发送时会抛异常,可以捕获异常进行处理 | |
| 149 | + */ | |
| 150 | + public static boolean sendDataToPrinter(Vector<Byte> vector) throws IOException { | |
| 151 | + if (portManager==null){ | |
| 152 | + return false; | |
| 153 | + } | |
| 154 | + return portManager.writeDataImmediately(vector); | |
| 155 | + } | |
| 156 | + /** | |
| 157 | + * 关闭连接 | |
| 158 | + * @return | |
| 159 | + */ | |
| 160 | + public static void close(){ | |
| 161 | + if (portManager!=null){ | |
| 162 | + portManager.closePort(); | |
| 163 | + portManager=null; | |
| 164 | + } | |
| 165 | + } | |
| 166 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/SerialPortDeviceActivity.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.Intent; | |
| 5 | +import android.os.Bundle; | |
| 6 | +import android.text.TextUtils; | |
| 7 | +import android.view.View; | |
| 8 | +import android.widget.AdapterView; | |
| 9 | +import android.widget.ArrayAdapter; | |
| 10 | +import android.widget.Button; | |
| 11 | +import android.widget.Spinner; | |
| 12 | + | |
| 13 | +import com.gprinter.utils.SerialPortFinder; | |
| 14 | + | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * Copyright (C), 2012-2019, 打印机有限公司 | |
| 18 | + * FileName: Printer | |
| 19 | + * Author: Circle | |
| 20 | + * Date: 2019/12/25 19:46 | |
| 21 | + * Description: 打印机使用单例 | |
| 22 | + */ | |
| 23 | +public class SerialPortDeviceActivity extends Activity { | |
| 24 | + public static String SERIALPORT_PATH="SERIAL_PORT_PATH"; | |
| 25 | + public static String SERIALPORT_BAUDRATE="SERIAL_PORT_BAUD_RATE"; | |
| 26 | + private SerialPortFinder mSerialPortFinder; | |
| 27 | + private String[] entries; | |
| 28 | + private String[] entryValues; | |
| 29 | + private Spinner spSerialPortPath; | |
| 30 | + private Spinner spBaudrate; | |
| 31 | + private String path; | |
| 32 | + private int selectBaudrate; | |
| 33 | + private String[] baudrates; | |
| 34 | + private Button btnConfirm; | |
| 35 | + | |
| 36 | + @Override | |
| 37 | + protected void onCreate(Bundle savedInstanceState) { | |
| 38 | + super.onCreate(savedInstanceState); | |
| 39 | + setContentView(R.layout.activity_serial_port); | |
| 40 | + baudrates = this.getResources().getStringArray(R.array.baudrate); | |
| 41 | + mSerialPortFinder = new SerialPortFinder(); | |
| 42 | + entries = mSerialPortFinder.getAllDevices(); | |
| 43 | + //获取串口路径 | |
| 44 | + entryValues = mSerialPortFinder.getAllDevicesPath(); | |
| 45 | + initView(); | |
| 46 | + initListener(); | |
| 47 | + } | |
| 48 | + | |
| 49 | + private void initView() { | |
| 50 | + //串口路径初始化 | |
| 51 | + spSerialPortPath = (Spinner) findViewById(R.id.sp_serialport_path); | |
| 52 | + ArrayAdapter arrayAdapter; | |
| 53 | + if (entries != null) { | |
| 54 | + arrayAdapter = new ArrayAdapter(this, R.layout.text_item, entries); | |
| 55 | + } else { | |
| 56 | + arrayAdapter = new ArrayAdapter(this, R.layout.text_item, new String[]{this.getString(R.string.str_no_serialport)}); | |
| 57 | + } | |
| 58 | + spSerialPortPath.setAdapter(arrayAdapter); | |
| 59 | + //波特率数据初始化 | |
| 60 | + spBaudrate = (Spinner) findViewById(R.id.sp_baudrate); | |
| 61 | + ArrayAdapter portAdapter = new ArrayAdapter(this, R.layout.text_item, this.getResources().getStringArray(R.array.baudrate)); | |
| 62 | + spBaudrate.setAdapter(portAdapter); | |
| 63 | + btnConfirm = (Button) findViewById(R.id.btn_confirm); | |
| 64 | + } | |
| 65 | + | |
| 66 | + private void initListener() { | |
| 67 | + spBaudrate.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { | |
| 68 | + @Override | |
| 69 | + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { | |
| 70 | + //保存当前选择的波特率 | |
| 71 | + selectBaudrate = Integer.parseInt(baudrates[position]); | |
| 72 | + } | |
| 73 | + | |
| 74 | + @Override | |
| 75 | + public void onNothingSelected(AdapterView<?> parent) { | |
| 76 | + | |
| 77 | + } | |
| 78 | + }); | |
| 79 | + | |
| 80 | + spSerialPortPath.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { | |
| 81 | + @Override | |
| 82 | + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { | |
| 83 | + if (entryValues != null) { | |
| 84 | + path = entryValues[position]; | |
| 85 | + } | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Override | |
| 89 | + public void onNothingSelected(AdapterView<?> parent) { | |
| 90 | + | |
| 91 | + } | |
| 92 | + }); | |
| 93 | + | |
| 94 | + btnConfirm.setOnClickListener(new View.OnClickListener() { | |
| 95 | + @Override | |
| 96 | + public void onClick(View v) { | |
| 97 | + if (!TextUtils.isEmpty(path)) { | |
| 98 | + Intent intent = new Intent(); | |
| 99 | + Bundle bundle = new Bundle(); | |
| 100 | + bundle.putString(SERIALPORT_PATH, path); | |
| 101 | + bundle.putInt(SERIALPORT_BAUDRATE, selectBaudrate); | |
| 102 | + intent.putExtras(bundle); | |
| 103 | + setResult(RESULT_OK, intent); | |
| 104 | + } | |
| 105 | + finish(); | |
| 106 | + } | |
| 107 | + }); | |
| 108 | + } | |
| 109 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/SharedPreferencesUtil.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.content.SharedPreferences; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * Created by Administrator | |
| 8 | + */ | |
| 9 | +public class SharedPreferencesUtil { | |
| 10 | + private static final String NAME = "Configs"; | |
| 11 | + private static SharedPreferences sharedPreferences; | |
| 12 | + private static SharedPreferencesUtil sharedPreferencesUtil; | |
| 13 | + | |
| 14 | + private SharedPreferencesUtil(){ | |
| 15 | + | |
| 16 | + } | |
| 17 | + | |
| 18 | + public static SharedPreferencesUtil getInstantiation(Context context) { | |
| 19 | + if (sharedPreferences == null) { | |
| 20 | + sharedPreferencesUtil = new SharedPreferencesUtil(); | |
| 21 | + getSharedPreferences(context); | |
| 22 | + } | |
| 23 | + return sharedPreferencesUtil; | |
| 24 | + } | |
| 25 | + | |
| 26 | + private static void getSharedPreferences(Context context) { | |
| 27 | + sharedPreferences = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); | |
| 28 | + } | |
| 29 | + | |
| 30 | + public void putInt(int value, String key) { | |
| 31 | + sharedPreferences.edit().putInt(key, value).apply(); | |
| 32 | + } | |
| 33 | + | |
| 34 | + public void putString(String value, String key) { | |
| 35 | + sharedPreferences.edit().putString(key, value).apply(); | |
| 36 | + } | |
| 37 | + | |
| 38 | + public void putBoolean(boolean value, String key) { | |
| 39 | + sharedPreferences.edit().putBoolean(key, value).apply(); | |
| 40 | + } | |
| 41 | + | |
| 42 | + public void putFloat(float value, String key) { | |
| 43 | + sharedPreferences.edit().putFloat(key, value).apply(); | |
| 44 | + } | |
| 45 | + | |
| 46 | + public void putLong(long value, String key) { | |
| 47 | + sharedPreferences.edit().putLong(key, value).apply(); | |
| 48 | + } | |
| 49 | + | |
| 50 | + public int getInt(int defaultValue, String key) { | |
| 51 | + return sharedPreferences.getInt(key, defaultValue); | |
| 52 | + } | |
| 53 | + | |
| 54 | + public String getString(String defaultValue, String key) { | |
| 55 | + return sharedPreferences.getString(key, defaultValue); | |
| 56 | + } | |
| 57 | + | |
| 58 | + public boolean getBoolean(boolean defaultValue, String key) { | |
| 59 | + return sharedPreferences.getBoolean(key, defaultValue); | |
| 60 | + } | |
| 61 | + | |
| 62 | + public float getFloat(float defaultValue, String key) { | |
| 63 | + return sharedPreferences.getFloat(key, defaultValue); | |
| 64 | + } | |
| 65 | + | |
| 66 | + public void clear() { | |
| 67 | + sharedPreferences.edit().clear().apply(); | |
| 68 | + } | |
| 69 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/ThreadFactoryBuilder.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | + | |
| 4 | +import java.util.concurrent.ThreadFactory; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * Created by Circle on 2018/5/24. | |
| 8 | + */ | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 作者: Circle | |
| 12 | + * 创造于 2018/5/24. | |
| 13 | + */ | |
| 14 | +public class ThreadFactoryBuilder implements ThreadFactory { | |
| 15 | + | |
| 16 | + private String name; | |
| 17 | + private int counter; | |
| 18 | + | |
| 19 | + public ThreadFactoryBuilder(String name) { | |
| 20 | + this.name = name; | |
| 21 | + counter = 1; | |
| 22 | + } | |
| 23 | + | |
| 24 | + @Override | |
| 25 | + public Thread newThread(Runnable runnable) { | |
| 26 | + Thread thread = new Thread(runnable, name); | |
| 27 | + thread.setName("ThreadFactoryBuilder_" + name + "_" + counter); | |
| 28 | + return thread; | |
| 29 | + } | |
| 30 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/ThreadPoolManager.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import android.util.Log; | |
| 4 | + | |
| 5 | +import java.util.concurrent.ArrayBlockingQueue; | |
| 6 | +import java.util.concurrent.LinkedBlockingDeque; | |
| 7 | +import java.util.concurrent.RejectedExecutionHandler; | |
| 8 | +import java.util.concurrent.ThreadPoolExecutor; | |
| 9 | +import java.util.concurrent.TimeUnit; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * Created by Circle on 2019/9/17. | |
| 13 | + * 线程池管理类 | |
| 14 | + */ | |
| 15 | + | |
| 16 | +public class ThreadPoolManager { | |
| 17 | + String TAG=ThreadPoolManager.class.getSimpleName(); | |
| 18 | + private static ThreadPoolManager threadPoolManager=null; | |
| 19 | + | |
| 20 | + public static ThreadPoolManager getInstance(){ | |
| 21 | + if (threadPoolManager == null) { | |
| 22 | + threadPoolManager =new ThreadPoolManager(); | |
| 23 | + } | |
| 24 | + return threadPoolManager; | |
| 25 | + } | |
| 26 | + //线程安全 | |
| 27 | + private LinkedBlockingDeque<Runnable> mQueue=new LinkedBlockingDeque<>(); | |
| 28 | + | |
| 29 | + private ThreadPoolExecutor mThreadPoolExecutor; | |
| 30 | + //创建线程池 | |
| 31 | + private ThreadPoolManager(){ | |
| 32 | + mThreadPoolExecutor=new ThreadPoolExecutor(1, 100, 15, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), new RejectedExecutionHandler() { | |
| 33 | + @Override | |
| 34 | + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { | |
| 35 | + addTask(r); | |
| 36 | + } | |
| 37 | + }); | |
| 38 | + new Thread(coreTread).start(); | |
| 39 | +// mThreadPoolExecutor.execute(coreTread);//执行核心线程池 | |
| 40 | + } | |
| 41 | + | |
| 42 | + | |
| 43 | + //将请求添加到队列中 | |
| 44 | + public void addTask(Runnable runnable){ | |
| 45 | + Log.e(TAG,runnable.getClass().getSimpleName()); | |
| 46 | + if (runnable!=null){ | |
| 47 | + try { | |
| 48 | + mQueue.put(runnable); | |
| 49 | + } catch (InterruptedException e) { | |
| 50 | + e.printStackTrace(); | |
| 51 | + } | |
| 52 | + } | |
| 53 | + } | |
| 54 | + public void addTopTask(Runnable runnable){ | |
| 55 | + if (runnable!=null){ | |
| 56 | + try { | |
| 57 | + mQueue.putFirst(runnable); | |
| 58 | + } catch (InterruptedException e) { | |
| 59 | + e.printStackTrace(); | |
| 60 | + } | |
| 61 | + } | |
| 62 | + } | |
| 63 | + //创建核心线程 | |
| 64 | + public Runnable coreTread=new Runnable() { | |
| 65 | + Runnable runn=null; | |
| 66 | + @Override | |
| 67 | + public void run() { | |
| 68 | + while (true) { | |
| 69 | + try { | |
| 70 | + runn = mQueue.take(); | |
| 71 | + synchronized(this){ | |
| 72 | + mThreadPoolExecutor.execute(runn);//执行线程 | |
| 73 | + } | |
| 74 | + } catch (InterruptedException e) { | |
| 75 | + e.printStackTrace(); | |
| 76 | + } | |
| 77 | + } | |
| 78 | + } | |
| 79 | + }; | |
| 80 | + | |
| 81 | + /** | |
| 82 | + * 结束线程池 | |
| 83 | + */ | |
| 84 | + public void stopThreadPool() { | |
| 85 | + if (mThreadPoolExecutor != null) { | |
| 86 | + mThreadPoolExecutor.shutdown(); | |
| 87 | + mQueue.clear(); | |
| 88 | + mThreadPoolExecutor = null; | |
| 89 | + threadPoolManager = null; | |
| 90 | + } | |
| 91 | + } | |
| 92 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/UsbDeviceActivity.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.content.Intent; | |
| 6 | +import android.hardware.usb.UsbDevice; | |
| 7 | +import android.hardware.usb.UsbManager; | |
| 8 | +import android.os.Bundle; | |
| 9 | +import android.util.Log; | |
| 10 | +import android.view.View; | |
| 11 | +import android.widget.AdapterView; | |
| 12 | +import android.widget.ArrayAdapter; | |
| 13 | +import android.widget.ListView; | |
| 14 | +import android.widget.TextView; | |
| 15 | + | |
| 16 | +import androidx.appcompat.app.AppCompatActivity; | |
| 17 | + | |
| 18 | +import java.util.HashMap; | |
| 19 | +import java.util.Iterator; | |
| 20 | + | |
| 21 | +/** | |
| 22 | + * Copyright (C), 2012-2020, 打印机有限公司 | |
| 23 | + * FileName: UsbDeviceListActivity | |
| 24 | + * Author: Circle | |
| 25 | + * Date: 2020/7/18 16:17 | |
| 26 | + * Description: 获取USB设备列表 | |
| 27 | + */ | |
| 28 | +public class UsbDeviceActivity extends AppCompatActivity { | |
| 29 | + | |
| 30 | + private static final String TAG = UsbDeviceActivity.class.getSimpleName(); | |
| 31 | + /** | |
| 32 | + * Member fields | |
| 33 | + */ | |
| 34 | + private ListView lvDevices = null; | |
| 35 | + private ArrayAdapter<String> adapter; | |
| 36 | + public static final String USB_NAME = "usb_name"; | |
| 37 | + UsbManager manager; | |
| 38 | + | |
| 39 | + @Override | |
| 40 | + protected void onCreate(Bundle savedInstanceState) { | |
| 41 | + super.onCreate(savedInstanceState); | |
| 42 | + setContentView(R.layout.activity_usb); | |
| 43 | + setTitle(getString(R.string.usb_label)); | |
| 44 | + initView(); | |
| 45 | + getUsbDeviceList(); | |
| 46 | + } | |
| 47 | + /** | |
| 48 | + * 初始化视图、控件 | |
| 49 | + */ | |
| 50 | + private void initView() { | |
| 51 | + lvDevices = (ListView) findViewById(R.id.lv_usb); | |
| 52 | + adapter = new ArrayAdapter<String>(this,R.layout.text_item); | |
| 53 | + lvDevices.setOnItemClickListener(mDeviceClickListener); | |
| 54 | + lvDevices.setAdapter(adapter); | |
| 55 | + } | |
| 56 | + | |
| 57 | + /** | |
| 58 | + * 检查USB设备的PID与VID | |
| 59 | + * @param dev | |
| 60 | + * @return | |
| 61 | + */ | |
| 62 | + boolean checkUsbDevicePidVid(UsbDevice dev) { | |
| 63 | + int pid = dev.getProductId(); | |
| 64 | + int vid = dev.getVendorId(); | |
| 65 | + return ((vid == 34918 && pid == 256) || (vid == 1137 && pid == 85) | |
| 66 | + || (vid == 6790 && pid == 30084) | |
| 67 | + || (vid == 26728 && pid == 256) || (vid == 26728 && pid == 512) | |
| 68 | + || (vid == 26728 && pid == 256) || (vid == 26728 && pid == 768) | |
| 69 | + || (vid == 26728 && pid == 1024) || (vid == 26728 && pid == 1280) | |
| 70 | + || (vid == 26728 && pid == 1536)); | |
| 71 | + } | |
| 72 | + | |
| 73 | + /** | |
| 74 | + * 获取USB设备列表 | |
| 75 | + */ | |
| 76 | + public void getUsbDeviceList() { | |
| 77 | + manager = (UsbManager) getSystemService(Context.USB_SERVICE); | |
| 78 | + // Get the list of attached devices | |
| 79 | + HashMap<String, UsbDevice> devices = manager.getDeviceList(); | |
| 80 | + Iterator<UsbDevice> deviceIterator = devices.values().iterator(); | |
| 81 | + int count = devices.size(); | |
| 82 | + Log.d(TAG, "count " + count); | |
| 83 | + if (count > 0) { | |
| 84 | + while (deviceIterator.hasNext()) { | |
| 85 | + UsbDevice device = deviceIterator.next(); | |
| 86 | + String devicename = device.getDeviceName(); | |
| 87 | + if (checkUsbDevicePidVid(device)) { | |
| 88 | + adapter.add(devicename); | |
| 89 | + } | |
| 90 | + } | |
| 91 | + } else { | |
| 92 | + String noDevices = getResources().getText(R.string.none_usb_device).toString(); | |
| 93 | + Log.d(TAG, "noDevices " + noDevices); | |
| 94 | + adapter.add(noDevices); | |
| 95 | + } | |
| 96 | + } | |
| 97 | + | |
| 98 | + private AdapterView.OnItemClickListener mDeviceClickListener = new AdapterView.OnItemClickListener() { | |
| 99 | + @Override | |
| 100 | + public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) { | |
| 101 | + // Cancel discovery because it's costly and we're about to connect | |
| 102 | + // Get the device MAC address, which is the last 17 chars in the View | |
| 103 | + String info = ((TextView) v).getText().toString(); | |
| 104 | + String noDevices = getResources().getText(R.string.none_usb_device).toString(); | |
| 105 | + if (!info.equals(noDevices)) { | |
| 106 | + String address = info; | |
| 107 | + // Create the result Intent and include the MAC address | |
| 108 | + Intent intent = new Intent(); | |
| 109 | + intent.putExtra(USB_NAME, address); | |
| 110 | + // Set result and finish this Activity | |
| 111 | + setResult(Activity.RESULT_OK, intent); | |
| 112 | + } | |
| 113 | + finish(); | |
| 114 | + } | |
| 115 | + }; | |
| 116 | + | |
| 117 | + @Override | |
| 118 | + protected void onDestroy() { | |
| 119 | + super.onDestroy(); | |
| 120 | + if (manager!=null){ | |
| 121 | + manager=null; | |
| 122 | + } | |
| 123 | + } | |
| 124 | +} | |
| 0 | 125 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/Utils.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.content.pm.PackageInfo; | |
| 5 | +import android.content.pm.PackageManager; | |
| 6 | +import android.hardware.usb.UsbDevice; | |
| 7 | +import android.hardware.usb.UsbManager; | |
| 8 | +import android.widget.Toast; | |
| 9 | + | |
| 10 | +import java.io.ByteArrayOutputStream; | |
| 11 | +import java.io.IOException; | |
| 12 | +import java.io.InputStream; | |
| 13 | +import java.util.Arrays; | |
| 14 | +import java.util.HashMap; | |
| 15 | +import java.util.regex.Matcher; | |
| 16 | +import java.util.regex.Pattern; | |
| 17 | + | |
| 18 | +/** | |
| 19 | + * 作者: Circle | |
| 20 | + * 创造于 2018/5/24. | |
| 21 | + */ | |
| 22 | +public class Utils { | |
| 23 | + /** | |
| 24 | + * 通过USB名称获取设备 | |
| 25 | + * @param context | |
| 26 | + * @param usbName | |
| 27 | + * @return | |
| 28 | + */ | |
| 29 | + public static UsbDevice getUsbDeviceFromName(Context context, String usbName) { | |
| 30 | + UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); | |
| 31 | + HashMap<String, UsbDevice> usbDeviceList = usbManager.getDeviceList(); | |
| 32 | + return usbDeviceList.get(usbName); | |
| 33 | + } | |
| 34 | + /** | |
| 35 | + * 短时间吐司 | |
| 36 | + * @param context | |
| 37 | + * @param msg | |
| 38 | + */ | |
| 39 | + public static void shortToast(Context context,String msg){ | |
| 40 | + Toast.makeText(context,msg,Toast.LENGTH_SHORT).show(); | |
| 41 | + } | |
| 42 | + /** | |
| 43 | + * 合拼两个数组 | |
| 44 | + * @param first | |
| 45 | + * @param second | |
| 46 | + * @param <T> | |
| 47 | + * @return | |
| 48 | + */ | |
| 49 | + public static <T> byte[] concat(byte[] first, byte[] second) { | |
| 50 | + byte[] result = Arrays.copyOf(first, first.length + second.length); | |
| 51 | + System.arraycopy(second, 0, result, first.length, second.length); | |
| 52 | + return result; | |
| 53 | + } | |
| 54 | + /** | |
| 55 | + * 检测WiFi的IP是否输入正确 | |
| 56 | + * @param addr | |
| 57 | + * @return | |
| 58 | + */ | |
| 59 | + public static boolean checkIP(String addr) | |
| 60 | + | |
| 61 | + { | |
| 62 | + if(addr.length() < 7 || addr.length() > 15 || "".equals(addr)) | |
| 63 | + { | |
| 64 | + return false; | |
| 65 | + } | |
| 66 | + /** | |
| 67 | + * 判断IP格式和范围 | |
| 68 | + */ | |
| 69 | + String rexp = "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}"; | |
| 70 | + Pattern pat = Pattern.compile(rexp); | |
| 71 | + Matcher mat = pat.matcher(addr); | |
| 72 | + boolean ipAddress = mat.find(); | |
| 73 | + //============对之前的ip判断的bug在进行判断 | |
| 74 | + if (ipAddress==true){ | |
| 75 | + String ips[] = addr.split("\\."); | |
| 76 | + if(ips.length==4){ | |
| 77 | + try{ | |
| 78 | + for(String ip : ips){ | |
| 79 | + if(Integer.parseInt(ip)<0||Integer.parseInt(ip)>255){ | |
| 80 | + return false; | |
| 81 | + } | |
| 82 | + } | |
| 83 | + }catch (Exception e){ | |
| 84 | + return false; | |
| 85 | + } | |
| 86 | + return true; | |
| 87 | + }else{ | |
| 88 | + return false; | |
| 89 | + } | |
| 90 | + } | |
| 91 | + return ipAddress; | |
| 92 | + } | |
| 93 | + /** | |
| 94 | + * 获取指定包名的版本号 | |
| 95 | + * | |
| 96 | + * @param context | |
| 97 | + * 本应用程序上下文 | |
| 98 | + * @return | |
| 99 | + * @throws Exception | |
| 100 | + */ | |
| 101 | + public static String getVersionName(Context context) { | |
| 102 | + // 获取packagemanager的实例 | |
| 103 | + PackageManager packageManager = context.getPackageManager(); | |
| 104 | + try { | |
| 105 | + PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0); | |
| 106 | + String version = packInfo.versionName; | |
| 107 | + return version; | |
| 108 | + } catch (PackageManager.NameNotFoundException e) { | |
| 109 | + e.printStackTrace(); | |
| 110 | + } | |
| 111 | + return ""; | |
| 112 | + | |
| 113 | + } | |
| 114 | + public static byte [] getAssetsFile(Context context,String fileName) { | |
| 115 | + InputStream in = null; | |
| 116 | + ByteArrayOutputStream outStream = null; | |
| 117 | + byte [] data=null; | |
| 118 | + try { | |
| 119 | + in = context.getResources().getAssets().open(fileName); | |
| 120 | + outStream = new ByteArrayOutputStream(); | |
| 121 | + //创建byte数组 | |
| 122 | + byte[] buffer = new byte[1024*1024]; | |
| 123 | + //将文件中的数据读到byte数组中 | |
| 124 | + int len = 0; | |
| 125 | + while ((len = in.read(buffer)) != -1) { | |
| 126 | + outStream.write(buffer, 0, len); | |
| 127 | + } | |
| 128 | + data = outStream.toByteArray(); | |
| 129 | + return data; | |
| 130 | + } catch (IOException e) { | |
| 131 | + e.printStackTrace(); | |
| 132 | + }finally { | |
| 133 | + try { | |
| 134 | + if (in!=null){ | |
| 135 | + in.close(); | |
| 136 | + } | |
| 137 | + if (outStream!=null){ | |
| 138 | + outStream.close(); | |
| 139 | + } | |
| 140 | + } catch (IOException e) { | |
| 141 | + e.printStackTrace(); | |
| 142 | + } | |
| 143 | + } | |
| 144 | + return data; | |
| 145 | + } | |
| 146 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/WifiDeviceActivity.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.content.Intent; | |
| 6 | +import android.os.Bundle; | |
| 7 | +import android.text.TextUtils; | |
| 8 | +import android.view.View; | |
| 9 | +import android.widget.Button; | |
| 10 | +import android.widget.EditText; | |
| 11 | + | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * Copyright (C), 2012-2019, 打印机有限公司 | |
| 15 | + * FileName: Printer | |
| 16 | + * Author: Circle | |
| 17 | + * Date: 2019/12/25 19:46 | |
| 18 | + * Description: WIFI打印机设备 | |
| 19 | + */ | |
| 20 | +public class WifiDeviceActivity extends Activity { | |
| 21 | + public static String IP="IP"; | |
| 22 | + public Context context; | |
| 23 | + public EditText edWifi; | |
| 24 | + public Button btnOK; | |
| 25 | + SharedPreferencesUtil sharedPreferencesUtil; | |
| 26 | + @Override | |
| 27 | + protected void onCreate(Bundle savedInstanceState) { | |
| 28 | + super.onCreate(savedInstanceState); | |
| 29 | + setContentView(R.layout.activity_wifi); | |
| 30 | + context=WifiDeviceActivity.this; | |
| 31 | + sharedPreferencesUtil= SharedPreferencesUtil.getInstantiation(context); | |
| 32 | + initView(); | |
| 33 | + } | |
| 34 | + | |
| 35 | + private void initView() { | |
| 36 | + //串口路径初始化 | |
| 37 | + edWifi = (EditText)findViewById(R.id.et_wifi_ip); | |
| 38 | + btnOK=(Button)findViewById(R.id.btn_confirm) ; | |
| 39 | + String ip = sharedPreferencesUtil.getString("192.168.123.100", IP); | |
| 40 | + edWifi.setText(ip); | |
| 41 | + initListener(); | |
| 42 | + } | |
| 43 | + | |
| 44 | + private void initListener() { | |
| 45 | + btnOK.setOnClickListener(new View.OnClickListener() { | |
| 46 | + @Override | |
| 47 | + public void onClick(View v) { | |
| 48 | + String ip=edWifi.getText().toString().trim(); | |
| 49 | + if (TextUtils.isEmpty(ip)&&!Utils.checkIP(ip)){//ip不合法 | |
| 50 | + Utils.shortToast(context,context.getString(R.string.ip_is_illegal)); | |
| 51 | + return; | |
| 52 | + } | |
| 53 | + sharedPreferencesUtil.putString(ip,IP); | |
| 54 | + Intent intent = new Intent(); | |
| 55 | + Bundle bundle = new Bundle(); | |
| 56 | + bundle.putString(IP, ip); | |
| 57 | + intent.putExtras(bundle); | |
| 58 | + setResult(RESULT_OK, intent); | |
| 59 | + finish(); | |
| 60 | + } | |
| 61 | + }); | |
| 62 | + } | |
| 63 | + | |
| 64 | + @Override | |
| 65 | + protected void onDestroy() { | |
| 66 | + super.onDestroy(); | |
| 67 | + } | |
| 68 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/bean/BluetoothParameter.java
0 → 100755
| 1 | +package com.printer.tscdemo.bean; | |
| 2 | + | |
| 3 | +public class BluetoothParameter { | |
| 4 | + private String bluetoothName; | |
| 5 | + private String bluetoothMac; | |
| 6 | + private String bluetoothStrength;/*蓝牙强度*/ | |
| 7 | + public String getBluetoothName() { | |
| 8 | + return bluetoothName; | |
| 9 | + } | |
| 10 | + | |
| 11 | + public void setBluetoothName(String bluetoothName) { | |
| 12 | + this.bluetoothName = bluetoothName; | |
| 13 | + } | |
| 14 | + | |
| 15 | + public String getBluetoothMac() { | |
| 16 | + return bluetoothMac; | |
| 17 | + } | |
| 18 | + | |
| 19 | + public void setBluetoothMac(String bluetoothMac) { | |
| 20 | + this.bluetoothMac = bluetoothMac; | |
| 21 | + } | |
| 22 | + | |
| 23 | + public String getBluetoothStrength() { | |
| 24 | + return bluetoothStrength; | |
| 25 | + } | |
| 26 | + | |
| 27 | + public void setBluetoothStrength(String bluetoothStrength) { | |
| 28 | + this.bluetoothStrength = bluetoothStrength; | |
| 29 | + } | |
| 30 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/jniLibs/arm64-v8a/libserial_port.so
0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/jniLibs/armeabi-v7a/libserial_port.so
0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/jniLibs/armeabi/libserial_port.so
0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
0 → 100755
| 1 | +<vector xmlns:android="http://schemas.android.com/apk/res/android" | |
| 2 | + xmlns:aapt="http://schemas.android.com/aapt" | |
| 3 | + android:width="108dp" | |
| 4 | + android:height="108dp" | |
| 5 | + android:viewportWidth="108" | |
| 6 | + android:viewportHeight="108"> | |
| 7 | + <path | |
| 8 | + android:fillType="evenOdd" | |
| 9 | + android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" | |
| 10 | + android:strokeWidth="1" | |
| 11 | + android:strokeColor="#00000000"> | |
| 12 | + <aapt:attr name="android:fillColor"> | |
| 13 | + <gradient | |
| 14 | + android:endX="78.5885" | |
| 15 | + android:endY="90.9159" | |
| 16 | + android:startX="48.7653" | |
| 17 | + android:startY="61.0927" | |
| 18 | + android:type="linear"> | |
| 19 | + <item | |
| 20 | + android:color="#44000000" | |
| 21 | + android:offset="0.0" /> | |
| 22 | + <item | |
| 23 | + android:color="#00000000" | |
| 24 | + android:offset="1.0" /> | |
| 25 | + </gradient> | |
| 26 | + </aapt:attr> | |
| 27 | + </path> | |
| 28 | + <path | |
| 29 | + android:fillColor="#FFFFFF" | |
| 30 | + android:fillType="nonZero" | |
| 31 | + android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" | |
| 32 | + android:strokeWidth="1" | |
| 33 | + android:strokeColor="#00000000" /> | |
| 34 | +</vector> | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/drawable/flower.jpg
0 → 100755
15.3 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/drawable/head.jpg
0 → 100755
28.6 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/drawable/ic_launcher_background.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<vector xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:width="108dp" | |
| 4 | + android:height="108dp" | |
| 5 | + android:viewportWidth="108" | |
| 6 | + android:viewportHeight="108"> | |
| 7 | + <path | |
| 8 | + android:fillColor="#008577" | |
| 9 | + android:pathData="M0,0h108v108h-108z" /> | |
| 10 | + <path | |
| 11 | + android:fillColor="#00000000" | |
| 12 | + android:pathData="M9,0L9,108" | |
| 13 | + android:strokeWidth="0.8" | |
| 14 | + android:strokeColor="#33FFFFFF" /> | |
| 15 | + <path | |
| 16 | + android:fillColor="#00000000" | |
| 17 | + android:pathData="M19,0L19,108" | |
| 18 | + android:strokeWidth="0.8" | |
| 19 | + android:strokeColor="#33FFFFFF" /> | |
| 20 | + <path | |
| 21 | + android:fillColor="#00000000" | |
| 22 | + android:pathData="M29,0L29,108" | |
| 23 | + android:strokeWidth="0.8" | |
| 24 | + android:strokeColor="#33FFFFFF" /> | |
| 25 | + <path | |
| 26 | + android:fillColor="#00000000" | |
| 27 | + android:pathData="M39,0L39,108" | |
| 28 | + android:strokeWidth="0.8" | |
| 29 | + android:strokeColor="#33FFFFFF" /> | |
| 30 | + <path | |
| 31 | + android:fillColor="#00000000" | |
| 32 | + android:pathData="M49,0L49,108" | |
| 33 | + android:strokeWidth="0.8" | |
| 34 | + android:strokeColor="#33FFFFFF" /> | |
| 35 | + <path | |
| 36 | + android:fillColor="#00000000" | |
| 37 | + android:pathData="M59,0L59,108" | |
| 38 | + android:strokeWidth="0.8" | |
| 39 | + android:strokeColor="#33FFFFFF" /> | |
| 40 | + <path | |
| 41 | + android:fillColor="#00000000" | |
| 42 | + android:pathData="M69,0L69,108" | |
| 43 | + android:strokeWidth="0.8" | |
| 44 | + android:strokeColor="#33FFFFFF" /> | |
| 45 | + <path | |
| 46 | + android:fillColor="#00000000" | |
| 47 | + android:pathData="M79,0L79,108" | |
| 48 | + android:strokeWidth="0.8" | |
| 49 | + android:strokeColor="#33FFFFFF" /> | |
| 50 | + <path | |
| 51 | + android:fillColor="#00000000" | |
| 52 | + android:pathData="M89,0L89,108" | |
| 53 | + android:strokeWidth="0.8" | |
| 54 | + android:strokeColor="#33FFFFFF" /> | |
| 55 | + <path | |
| 56 | + android:fillColor="#00000000" | |
| 57 | + android:pathData="M99,0L99,108" | |
| 58 | + android:strokeWidth="0.8" | |
| 59 | + android:strokeColor="#33FFFFFF" /> | |
| 60 | + <path | |
| 61 | + android:fillColor="#00000000" | |
| 62 | + android:pathData="M0,9L108,9" | |
| 63 | + android:strokeWidth="0.8" | |
| 64 | + android:strokeColor="#33FFFFFF" /> | |
| 65 | + <path | |
| 66 | + android:fillColor="#00000000" | |
| 67 | + android:pathData="M0,19L108,19" | |
| 68 | + android:strokeWidth="0.8" | |
| 69 | + android:strokeColor="#33FFFFFF" /> | |
| 70 | + <path | |
| 71 | + android:fillColor="#00000000" | |
| 72 | + android:pathData="M0,29L108,29" | |
| 73 | + android:strokeWidth="0.8" | |
| 74 | + android:strokeColor="#33FFFFFF" /> | |
| 75 | + <path | |
| 76 | + android:fillColor="#00000000" | |
| 77 | + android:pathData="M0,39L108,39" | |
| 78 | + android:strokeWidth="0.8" | |
| 79 | + android:strokeColor="#33FFFFFF" /> | |
| 80 | + <path | |
| 81 | + android:fillColor="#00000000" | |
| 82 | + android:pathData="M0,49L108,49" | |
| 83 | + android:strokeWidth="0.8" | |
| 84 | + android:strokeColor="#33FFFFFF" /> | |
| 85 | + <path | |
| 86 | + android:fillColor="#00000000" | |
| 87 | + android:pathData="M0,59L108,59" | |
| 88 | + android:strokeWidth="0.8" | |
| 89 | + android:strokeColor="#33FFFFFF" /> | |
| 90 | + <path | |
| 91 | + android:fillColor="#00000000" | |
| 92 | + android:pathData="M0,69L108,69" | |
| 93 | + android:strokeWidth="0.8" | |
| 94 | + android:strokeColor="#33FFFFFF" /> | |
| 95 | + <path | |
| 96 | + android:fillColor="#00000000" | |
| 97 | + android:pathData="M0,79L108,79" | |
| 98 | + android:strokeWidth="0.8" | |
| 99 | + android:strokeColor="#33FFFFFF" /> | |
| 100 | + <path | |
| 101 | + android:fillColor="#00000000" | |
| 102 | + android:pathData="M0,89L108,89" | |
| 103 | + android:strokeWidth="0.8" | |
| 104 | + android:strokeColor="#33FFFFFF" /> | |
| 105 | + <path | |
| 106 | + android:fillColor="#00000000" | |
| 107 | + android:pathData="M0,99L108,99" | |
| 108 | + android:strokeWidth="0.8" | |
| 109 | + android:strokeColor="#33FFFFFF" /> | |
| 110 | + <path | |
| 111 | + android:fillColor="#00000000" | |
| 112 | + android:pathData="M19,29L89,29" | |
| 113 | + android:strokeWidth="0.8" | |
| 114 | + android:strokeColor="#33FFFFFF" /> | |
| 115 | + <path | |
| 116 | + android:fillColor="#00000000" | |
| 117 | + android:pathData="M19,39L89,39" | |
| 118 | + android:strokeWidth="0.8" | |
| 119 | + android:strokeColor="#33FFFFFF" /> | |
| 120 | + <path | |
| 121 | + android:fillColor="#00000000" | |
| 122 | + android:pathData="M19,49L89,49" | |
| 123 | + android:strokeWidth="0.8" | |
| 124 | + android:strokeColor="#33FFFFFF" /> | |
| 125 | + <path | |
| 126 | + android:fillColor="#00000000" | |
| 127 | + android:pathData="M19,59L89,59" | |
| 128 | + android:strokeWidth="0.8" | |
| 129 | + android:strokeColor="#33FFFFFF" /> | |
| 130 | + <path | |
| 131 | + android:fillColor="#00000000" | |
| 132 | + android:pathData="M19,69L89,69" | |
| 133 | + android:strokeWidth="0.8" | |
| 134 | + android:strokeColor="#33FFFFFF" /> | |
| 135 | + <path | |
| 136 | + android:fillColor="#00000000" | |
| 137 | + android:pathData="M19,79L89,79" | |
| 138 | + android:strokeWidth="0.8" | |
| 139 | + android:strokeColor="#33FFFFFF" /> | |
| 140 | + <path | |
| 141 | + android:fillColor="#00000000" | |
| 142 | + android:pathData="M29,19L29,89" | |
| 143 | + android:strokeWidth="0.8" | |
| 144 | + android:strokeColor="#33FFFFFF" /> | |
| 145 | + <path | |
| 146 | + android:fillColor="#00000000" | |
| 147 | + android:pathData="M39,19L39,89" | |
| 148 | + android:strokeWidth="0.8" | |
| 149 | + android:strokeColor="#33FFFFFF" /> | |
| 150 | + <path | |
| 151 | + android:fillColor="#00000000" | |
| 152 | + android:pathData="M49,19L49,89" | |
| 153 | + android:strokeWidth="0.8" | |
| 154 | + android:strokeColor="#33FFFFFF" /> | |
| 155 | + <path | |
| 156 | + android:fillColor="#00000000" | |
| 157 | + android:pathData="M59,19L59,89" | |
| 158 | + android:strokeWidth="0.8" | |
| 159 | + android:strokeColor="#33FFFFFF" /> | |
| 160 | + <path | |
| 161 | + android:fillColor="#00000000" | |
| 162 | + android:pathData="M69,19L69,89" | |
| 163 | + android:strokeWidth="0.8" | |
| 164 | + android:strokeColor="#33FFFFFF" /> | |
| 165 | + <path | |
| 166 | + android:fillColor="#00000000" | |
| 167 | + android:pathData="M79,19L79,89" | |
| 168 | + android:strokeWidth="0.8" | |
| 169 | + android:strokeColor="#33FFFFFF" /> | |
| 170 | +</vector> | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/drawable/test.bmp
0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_blue_tooth_device_list.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + android:orientation="vertical" | |
| 6 | + xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 7 | + <ListView | |
| 8 | + android:id="@+id/lv_devices" | |
| 9 | + android:layout_width="match_parent" | |
| 10 | + android:layout_height="match_parent"/> | |
| 11 | +</LinearLayout> | |
| 0 | 12 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_bluetooth.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + android:orientation="vertical" | |
| 6 | + xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 7 | + <ListView | |
| 8 | + android:id="@+id/lv_devices" | |
| 9 | + android:layout_width="match_parent" | |
| 10 | + android:layout_height="0dp" | |
| 11 | + android:layout_weight="1"/> | |
| 12 | + <Button | |
| 13 | + android:id="@+id/btn_search" | |
| 14 | + android:layout_margin="4dp" | |
| 15 | + android:text="@string/search_bt" | |
| 16 | + android:layout_width="match_parent" | |
| 17 | + android:layout_height="wrap_content"/> | |
| 18 | +</LinearLayout> | |
| 0 | 19 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_main.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + android:orientation="vertical" | |
| 6 | + xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 7 | + <ScrollView | |
| 8 | + android:layout_width="match_parent" | |
| 9 | + android:layout_height="match_parent"> | |
| 10 | + <LinearLayout | |
| 11 | + android:layout_width="match_parent" | |
| 12 | + android:layout_height="match_parent" | |
| 13 | + android:orientation="vertical"> | |
| 14 | + <TextView | |
| 15 | + android:id="@+id/tvState" | |
| 16 | + android:text="@string/not_connected" | |
| 17 | + android:padding="8dp" | |
| 18 | + android:layout_width="match_parent" | |
| 19 | + android:layout_height="wrap_content"/> | |
| 20 | + <Button | |
| 21 | + android:onClick="blueToothDevices" | |
| 22 | + android:text="@string/blue_label" | |
| 23 | + android:layout_width="match_parent" | |
| 24 | + android:layout_height="wrap_content"/> | |
| 25 | + <Button | |
| 26 | + android:onClick="usbDevices" | |
| 27 | + android:text="@string/usb_label" | |
| 28 | + android:layout_width="match_parent" | |
| 29 | + android:layout_height="wrap_content"/> | |
| 30 | + <Button | |
| 31 | + android:onClick="wifiDevices" | |
| 32 | + android:text="@string/ethernet_label" | |
| 33 | + android:layout_width="match_parent" | |
| 34 | + android:layout_height="wrap_content"/> | |
| 35 | + <Button | |
| 36 | + android:onClick="serialPortDevices" | |
| 37 | + android:text="@string/serial_device" | |
| 38 | + android:layout_width="match_parent" | |
| 39 | + android:layout_height="wrap_content"/> | |
| 40 | + | |
| 41 | + <Button | |
| 42 | + android:layout_width="match_parent" | |
| 43 | + android:layout_height="wrap_content" | |
| 44 | + android:onClick="disconnect" | |
| 45 | + android:text="@string/disconnect" | |
| 46 | + android:textColor="@android:color/holo_red_dark" /> | |
| 47 | + <CheckBox | |
| 48 | + android:id="@+id/swState" | |
| 49 | + android:checked="false" | |
| 50 | + android:text="@string/print_state" | |
| 51 | + android:layout_width="wrap_content" | |
| 52 | + android:layout_height="wrap_content"/> | |
| 53 | + <LinearLayout | |
| 54 | + android:layout_width="match_parent" | |
| 55 | + android:layout_height="wrap_content" | |
| 56 | + android:orientation="horizontal" | |
| 57 | + android:padding="8dp"> | |
| 58 | + <TextView | |
| 59 | + android:text="@string/gap" | |
| 60 | + android:layout_width="wrap_content" | |
| 61 | + android:layout_height="wrap_content"> | |
| 62 | + </TextView> | |
| 63 | + <Spinner | |
| 64 | + android:id="@+id/sp_gap" | |
| 65 | + android:entries="@array/gap" | |
| 66 | + android:layout_width="0dp" | |
| 67 | + android:layout_weight="1" | |
| 68 | + android:layout_height="wrap_content"> | |
| 69 | + </Spinner> | |
| 70 | + </LinearLayout> | |
| 71 | + <Button | |
| 72 | + android:onClick="print" | |
| 73 | + android:text="@string/print_example" | |
| 74 | + android:layout_width="match_parent" | |
| 75 | + android:layout_height="wrap_content"/> | |
| 76 | + <Button | |
| 77 | + android:onClick="xml" | |
| 78 | + android:text="@string/print_xml" | |
| 79 | + android:layout_width="match_parent" | |
| 80 | + android:layout_height="wrap_content"/> | |
| 81 | + <Button | |
| 82 | + android:onClick="printPDF" | |
| 83 | + android:text="@string/print_pdf" | |
| 84 | + android:layout_width="match_parent" | |
| 85 | + android:layout_height="wrap_content"/> | |
| 86 | + <Button | |
| 87 | + android:onClick="checkState" | |
| 88 | + android:text="@string/check_state" | |
| 89 | + android:layout_width="match_parent" | |
| 90 | + android:layout_height="wrap_content"/> | |
| 91 | + </LinearLayout> | |
| 92 | + </ScrollView> | |
| 93 | +</LinearLayout> | |
| 0 | 94 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_serial_port.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<RelativeLayout android:layout_width="match_parent" | |
| 3 | + android:layout_height="wrap_content" | |
| 4 | + xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 5 | +<LinearLayout | |
| 6 | + android:orientation="vertical" | |
| 7 | + android:layout_width="match_parent" | |
| 8 | + android:layout_height="wrap_content"> | |
| 9 | + <TextView | |
| 10 | + android:layout_width="match_parent" | |
| 11 | + android:layout_height="wrap_content" | |
| 12 | + android:textSize="16sp" | |
| 13 | + android:text="@string/str_baudrate"/> | |
| 14 | + <Spinner | |
| 15 | + android:id="@+id/sp_baudrate" | |
| 16 | + android:layout_marginTop="12dp" | |
| 17 | + android:layout_width="match_parent" | |
| 18 | + android:layout_height="wrap_content"> | |
| 19 | + </Spinner> | |
| 20 | + <TextView | |
| 21 | + android:layout_width="match_parent" | |
| 22 | + android:layout_height="wrap_content" | |
| 23 | + android:textSize="16sp" | |
| 24 | + android:text="@string/str_serialport_path"/> | |
| 25 | + <Spinner | |
| 26 | + android:layout_marginTop="12dp" | |
| 27 | + android:id="@+id/sp_serialport_path" | |
| 28 | + android:layout_width="match_parent" | |
| 29 | + android:layout_height="wrap_content"> | |
| 30 | + </Spinner> | |
| 31 | + <Button | |
| 32 | + android:id="@+id/btn_confirm" | |
| 33 | + android:layout_marginTop="14dp" | |
| 34 | + android:layout_width="wrap_content" | |
| 35 | + android:layout_height="wrap_content" | |
| 36 | + android:layout_gravity="end" | |
| 37 | + android:text="@string/ok"/> | |
| 38 | +</LinearLayout> | |
| 39 | +</RelativeLayout> | |
| 0 | 40 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_usb.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:orientation="vertical" | |
| 5 | + android:layout_height="match_parent"> | |
| 6 | + <ListView | |
| 7 | + android:id="@+id/lv_usb" | |
| 8 | + android:layout_width="match_parent" | |
| 9 | + android:layout_height="0dp" | |
| 10 | + android:layout_weight="1" /> | |
| 11 | +</LinearLayout> | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_usb_list.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:orientation="vertical" | |
| 5 | + android:layout_height="match_parent"> | |
| 6 | + <ListView | |
| 7 | + android:id="@+id/lv_usb" | |
| 8 | + android:layout_width="match_parent" | |
| 9 | + android:layout_height="0dp" | |
| 10 | + android:layout_weight="1" /> | |
| 11 | +</LinearLayout> | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_wifi.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<RelativeLayout | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="wrap_content" | |
| 5 | + xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 6 | +<LinearLayout | |
| 7 | + android:layout_width="match_parent" | |
| 8 | + android:layout_height="wrap_content" | |
| 9 | + android:padding="8dp" | |
| 10 | + android:orientation="vertical"> | |
| 11 | + | |
| 12 | + <TextView | |
| 13 | + android:layout_marginTop="12dp" | |
| 14 | + android:layout_width="match_parent" | |
| 15 | + android:layout_height="wrap_content" | |
| 16 | + android:text="ip:" /> | |
| 17 | + | |
| 18 | + <EditText | |
| 19 | + android:id="@+id/et_wifi_ip" | |
| 20 | + android:digits="0123456789." | |
| 21 | + android:text="192.168.123.100" | |
| 22 | + android:layout_width="match_parent" | |
| 23 | + android:layout_height="wrap_content" /> | |
| 24 | + <Button | |
| 25 | + android:id="@+id/btn_confirm" | |
| 26 | + android:layout_marginTop="14dp" | |
| 27 | + android:layout_width="wrap_content" | |
| 28 | + android:layout_height="wrap_content" | |
| 29 | + android:layout_gravity="end" | |
| 30 | + android:text="@string/ok"/> | |
| 31 | +</LinearLayout> | |
| 32 | +</RelativeLayout> | |
| 0 | 33 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/bluetooth_list_item.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="wrap_content" | |
| 5 | + android:orientation="vertical" | |
| 6 | + android:padding="5dp" > | |
| 7 | + <LinearLayout | |
| 8 | + android:layout_width="wrap_content" | |
| 9 | + android:layout_height="wrap_content" | |
| 10 | + android:layout_centerVertical="true" | |
| 11 | + android:layout_marginLeft="5dp" | |
| 12 | + android:orientation="vertical" | |
| 13 | + android:padding="3dp" > | |
| 14 | + | |
| 15 | + <TextView | |
| 16 | + android:id="@+id/b_name" | |
| 17 | + android:textColor="@android:color/black" | |
| 18 | + android:layout_width="wrap_content" | |
| 19 | + android:layout_height="wrap_content" | |
| 20 | + android:text="name" | |
| 21 | + android:textSize="16sp" /> | |
| 22 | + | |
| 23 | + <TextView | |
| 24 | + android:id="@+id/b_mac" | |
| 25 | + android:textColor="@android:color/black" | |
| 26 | + android:layout_width="wrap_content" | |
| 27 | + android:layout_height="wrap_content" | |
| 28 | + android:text="mac" | |
| 29 | + android:textSize="15sp" /> | |
| 30 | + </LinearLayout> | |
| 31 | + | |
| 32 | + <TextView | |
| 33 | + android:id="@+id/b_info" | |
| 34 | + android:textColor="@android:color/black" | |
| 35 | + android:layout_width="wrap_content" | |
| 36 | + android:layout_height="wrap_content" | |
| 37 | + android:layout_alignParentRight="true" | |
| 38 | + android:layout_centerVertical="true" | |
| 39 | + android:layout_marginRight="10dp" | |
| 40 | + android:text="strength" | |
| 41 | + android:textSize="16sp" /> | |
| 42 | + | |
| 43 | +</RelativeLayout> | |
| 0 | 44 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/dialog_wifi_config.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + android:padding="8dp" | |
| 6 | + android:orientation="vertical"> | |
| 7 | + | |
| 8 | + <TextView | |
| 9 | + android:layout_marginTop="12dp" | |
| 10 | + android:layout_width="match_parent" | |
| 11 | + android:layout_height="wrap_content" | |
| 12 | + android:text="ip:" /> | |
| 13 | + | |
| 14 | + <EditText | |
| 15 | + android:id="@+id/et_wifi_ip" | |
| 16 | + android:digits="0123456789." | |
| 17 | + android:text="192.168.123.100" | |
| 18 | + android:layout_width="match_parent" | |
| 19 | + android:layout_height="wrap_content" /> | |
| 20 | + | |
| 21 | +</LinearLayout> | |
| 0 | 22 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/page.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:orientation="vertical" android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + android:padding="4dp" | |
| 6 | + android:background="@android:color/white"> | |
| 7 | + <TextView | |
| 8 | + android:layout_width="match_parent" | |
| 9 | + android:layout_height="wrap_content" | |
| 10 | + android:text="@string/Order" | |
| 11 | + android:textColor="@android:color/black" | |
| 12 | + android:gravity="center"/> | |
| 13 | + <View | |
| 14 | + android:background="@android:color/black" | |
| 15 | + android:layout_width="match_parent" | |
| 16 | + android:layout_height="2dp"/> | |
| 17 | + <ScrollView | |
| 18 | + android:layout_width="match_parent" | |
| 19 | + android:layout_height="wrap_content"> | |
| 20 | + <TableLayout | |
| 21 | + android:layout_width="match_parent" | |
| 22 | + android:stretchColumns="0" | |
| 23 | + android:layout_height="wrap_content" | |
| 24 | + android:id="@+id/line"> | |
| 25 | + <TableRow | |
| 26 | + android:layout_width="match_parent" | |
| 27 | + android:layout_height="wrap_content"> | |
| 28 | + <TextView | |
| 29 | + android:layout_width="wrap_content" | |
| 30 | + android:layout_height="wrap_content" | |
| 31 | + android:text="@string/name" | |
| 32 | + android:singleLine="false" | |
| 33 | + android:maxEms="3" | |
| 34 | + android:textColor="@android:color/black"/> | |
| 35 | + <TextView android:layout_width="wrap_content" | |
| 36 | + android:layout_height="wrap_content" | |
| 37 | + android:text="@string/Quantity" | |
| 38 | + android:layout_marginRight="15dp" | |
| 39 | + android:textColor="@android:color/black"/> | |
| 40 | + <TextView android:layout_width="wrap_content" | |
| 41 | + android:layout_height="wrap_content" | |
| 42 | + android:text="@string/price" | |
| 43 | + android:layout_marginRight="4dp" | |
| 44 | + android:textColor="@android:color/black" /> | |
| 45 | + </TableRow> | |
| 46 | + </TableLayout> | |
| 47 | + </ScrollView> | |
| 48 | + <View | |
| 49 | + android:background="@android:color/black" | |
| 50 | + android:layout_width="match_parent" | |
| 51 | + android:layout_height="2dp"/> | |
| 52 | + <LinearLayout | |
| 53 | + android:layout_width="match_parent" | |
| 54 | + android:layout_height="wrap_content" | |
| 55 | + android:orientation="horizontal"> | |
| 56 | + <TextView | |
| 57 | + android:layout_width="wrap_content" | |
| 58 | + android:layout_height="wrap_content" | |
| 59 | + android:text="@string/total_amount" | |
| 60 | + android:textColor="@android:color/black" /> | |
| 61 | + <TextView | |
| 62 | + android:layout_width="wrap_content" | |
| 63 | + android:layout_height="wrap_content" | |
| 64 | + android:id="@+id/total" | |
| 65 | + android:text="" | |
| 66 | + android:textColor="@android:color/black" /> | |
| 67 | + </LinearLayout> | |
| 68 | + <LinearLayout | |
| 69 | + android:layout_width="match_parent" | |
| 70 | + android:layout_height="wrap_content" | |
| 71 | + android:orientation="horizontal"> | |
| 72 | + <TextView | |
| 73 | + android:layout_width="wrap_content" | |
| 74 | + android:layout_height="wrap_content" | |
| 75 | + android:text="@string/cashier" | |
| 76 | + android:textColor="@android:color/black" | |
| 77 | + /> | |
| 78 | + <TextView | |
| 79 | + android:layout_width="wrap_content" | |
| 80 | + android:layout_height="wrap_content" | |
| 81 | + android:id="@+id/cashier" | |
| 82 | + android:text="" | |
| 83 | + android:textColor="@android:color/black" /> | |
| 84 | + </LinearLayout> | |
| 85 | +</LinearLayout> | |
| 0 | 86 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/text_item.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<TextView xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:id="@+id/text" | |
| 5 | + android:gravity="center" | |
| 6 | + android:layout_height="wrap_content" | |
| 7 | + android:textSize="18sp" | |
| 8 | + android:textColor="@color/colorPrimary" | |
| 9 | + android:padding="10dp" | |
| 10 | + /> | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 3 | + <background android:drawable="@drawable/ic_launcher_background" /> | |
| 4 | + <foreground android:drawable="@drawable/ic_launcher_foreground" /> | |
| 5 | +</adaptive-icon> | |
| 0 | 6 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
0 → 100755
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 3 | + <background android:drawable="@drawable/ic_launcher_background" /> | |
| 4 | + <foreground android:drawable="@drawable/ic_launcher_foreground" /> | |
| 5 | +</adaptive-icon> | |
| 0 | 6 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png
0 → 100755
2.89 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
0 → 100755
4.79 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png
0 → 100755
2.01 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
0 → 100755
2.72 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
0 → 100755
4.38 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
0 → 100755
6.73 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xhdpi/ic_priter.png
0 → 100755
4.1 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
0 → 100755
6.24 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
0 → 100755
10.2 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
0 → 100755
8.91 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
0 → 100755
14.8 KB
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/values-zh-rCN/strings.xml
0 → 100755
| 1 | +<resources> | |
| 2 | + <string name="app_name">标签Demo</string> | |
| 3 | + <string name="blue_label">蓝牙设备</string> | |
| 4 | + <string name="usb_label">USB设备</string> | |
| 5 | + <string name="ethernet_label">以太网设备</string> | |
| 6 | + <string name="serial_device">串口设备</string> | |
| 7 | + <string name="scan_connect">扫描连接</string> | |
| 8 | + <string name="disconnect">断开连接</string> | |
| 9 | + <string name="print_state">打印前查询状态</string> | |
| 10 | + <string name="print_example">打印案例(58mm*70mm)</string> | |
| 11 | + <string name="print_pdf">打印PDF</string> | |
| 12 | + <string name="print_menu">打印菜单</string> | |
| 13 | + <string name="print_xml">打印XML</string> | |
| 14 | + <string name="check_state">查询状态</string> | |
| 15 | + <string name="none_usb_device">无USB设备</string> | |
| 16 | + <string name="paired">已配对</string> | |
| 17 | + <string name="unpaired">未配对</string> | |
| 18 | + <string name="bluetooth_is_not_enabled">蓝牙未开启</string> | |
| 19 | + <string name="searching">搜索中...</string> | |
| 20 | + <string name="complete">搜索完成</string> | |
| 21 | + <string name="search_bt">搜索设备</string> | |
| 22 | + <string name="permission">权限</string> | |
| 23 | + <string name="no_permission">无定位权限</string> | |
| 24 | + <string name="pdf_error">获取PDF失败</string> | |
| 25 | + <string name="no_read">无读取文件权限</string> | |
| 26 | + <string name="camera">扫描需要摄像头权限</string> | |
| 27 | + <string name="gps_permission">安卓8.0系统搜索蓝牙需开启GPS定位功能!\n请选择是否开启</string> | |
| 28 | + <string name="camera_permission">获取权限失败,无法使用扫描功能</string> | |
| 29 | + <string name="tip">提示</string> | |
| 30 | + <string name="ok">确定</string> | |
| 31 | + <string name="cancel">取消</string> | |
| 32 | + <string name="status_error">状态获取异常</string> | |
| 33 | + <string name="status_fail">打印机状态获取失败,请检查打印机是否缺纸或开盖</string> | |
| 34 | + <string name="status_normal">状态正常</string> | |
| 35 | + <string name="status_feed">状态走纸、打印</string> | |
| 36 | + <string name="status_out_of_paper">状态缺纸</string> | |
| 37 | + <string name="status_open">状态开盖</string> | |
| 38 | + <string name="status_overheated">状态过热</string> | |
| 39 | + <string name="conn_first">请先连接打印机</string> | |
| 40 | + <string name="not_connected">未连接</string> | |
| 41 | + <string name="conn_fail">连接失败</string> | |
| 42 | + <string name="conn_success">连接成功</string> | |
| 43 | + <string name="conning">连接中...</string> | |
| 44 | + <string name="checking">查询中...</string> | |
| 45 | + <string name="conned">已连接</string> | |
| 46 | + <string name="print_success">打印成功</string> | |
| 47 | + <string name="print_fail">打印失败</string> | |
| 48 | + <string name="send_success">发送成功</string> | |
| 49 | + <string name="send_fail">发送失败</string> | |
| 50 | + <string name="str_baudrate">波特率</string> | |
| 51 | + <string name="str_serialport_path">串口地址</string> | |
| 52 | + <string-array name="baudrate"> | |
| 53 | + <item>9600</item> | |
| 54 | + <item>19200</item> | |
| 55 | + <item>38400</item> | |
| 56 | + <item>115200</item> | |
| 57 | + </string-array> | |
| 58 | + <string name="str_no_serialport">无串口设备</string> | |
| 59 | + <string name="ip_is_illegal">IP不合法</string> | |
| 60 | + <string name="Order">订单</string> | |
| 61 | + <string name="name">名称</string> | |
| 62 | + <string name="Quantity">数量</string> | |
| 63 | + <string name="price">价格</string> | |
| 64 | + <string name="total_amount">总金额:</string> | |
| 65 | + <string name="cashier">收银员:</string> | |
| 66 | + <string name="gap">标签间隙:</string> | |
| 67 | + <string-array name="gap"> | |
| 68 | + <item>0</item> | |
| 69 | + <item>1</item> | |
| 70 | + <item>2</item> | |
| 71 | + <item>3</item> | |
| 72 | + <item>4</item> | |
| 73 | + </string-array> | |
| 74 | +</resources> | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/values/colors.xml
0 → 100755
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/values/strings.xml
0 → 100755
| 1 | +<resources> | |
| 2 | + <string name="app_name">TscDemo</string> | |
| 3 | + <string name="blue_label">BlueTooth device</string> | |
| 4 | + <string name="usb_label">USB device</string> | |
| 5 | + <string name="ethernet_label">Ethernet device</string> | |
| 6 | + <string name="serial_device">Serial device</string> | |
| 7 | + <string name="scan_connect">Scan connection</string> | |
| 8 | + <string name="disconnect">Disconnect</string> | |
| 9 | + <string name="print_state">Check status before printing</string> | |
| 10 | + <string name="print_example">Print case(58mm*70mm)</string> | |
| 11 | + <string name="print_pdf">Print PDF</string> | |
| 12 | + <string name="print_menu">Print Menu</string> | |
| 13 | + <string name="print_xml">Print XML</string> | |
| 14 | + <string name="check_state">Check status</string> | |
| 15 | + <string name="none_usb_device">No USB device</string> | |
| 16 | + <string name="paired">Paired</string> | |
| 17 | + <string name="unpaired">Unpaired</string> | |
| 18 | + <string name="bluetooth_is_not_enabled">Bluetooth is not turned on</string> | |
| 19 | + <string name="searching">searching...</string> | |
| 20 | + <string name="search_bt">Search device</string> | |
| 21 | + <string name="complete">Search complete</string> | |
| 22 | + <string name="permission">Authority</string> | |
| 23 | + <string name="no_permission">No positioning permission</string> | |
| 24 | + <string name="pdf_error">Failed to get PDF</string> | |
| 25 | + <string name="no_read">No read file permission</string> | |
| 26 | + <string name="camera">Scanning requires camera permission</string> | |
| 27 | + <string name="gps_permission">Android 8.0 system needs to turn on GPS positioning function to search for Bluetooth!\nPlease choose whether to enable</string> | |
| 28 | + <string name="camera_permission">Failed to obtain permission, unable to use scanning function</string> | |
| 29 | + <string name="tip">Tip</string> | |
| 30 | + <string name="ok">OK</string> | |
| 31 | + <string name="cancel">Cancel</string> | |
| 32 | + <string name="status_error">Status acquisition exception</string> | |
| 33 | + <string name="status_fail">Failed to obtain the printer status, please check whether the printer is out of paper or open the cover</string> | |
| 34 | + <string name="status_normal">Normal state</string> | |
| 35 | + <string name="status_feed">Status feed、printing</string> | |
| 36 | + <string name="status_out_of_paper">Out of paper status</string> | |
| 37 | + <string name="status_open">Status open</string> | |
| 38 | + <string name="status_overheated">State overheated</string> | |
| 39 | + <string name="conn_first">Please connect the printer first</string> | |
| 40 | + <string name="not_connected">not connected</string> | |
| 41 | + <string name="conn_fail">Connection failed</string> | |
| 42 | + <string name="conn_success">connection succeeded</string> | |
| 43 | + <string name="conning">connecting...</string> | |
| 44 | + <string name="checking">Querying...</string> | |
| 45 | + <string name="conned">connected</string> | |
| 46 | + <string name="print_success">Print successfully</string> | |
| 47 | + <string name="print_fail">Print failed</string> | |
| 48 | + <string name="send_success">Send successfully</string> | |
| 49 | + <string name="send_fail">Send failed</string> | |
| 50 | + <string name="str_baudrate">BaudRate</string> | |
| 51 | + <string name="str_serialport_path">Serial port address</string> | |
| 52 | + <string-array name="baudrate"> | |
| 53 | + <item>9600</item> | |
| 54 | + <item>19200</item> | |
| 55 | + <item>38400</item> | |
| 56 | + <item>115200</item> | |
| 57 | + </string-array> | |
| 58 | + <string name="str_no_serialport">No serial device</string> | |
| 59 | + <string name="ip_is_illegal">ip is illegal</string> | |
| 60 | + <string name="Order">Order</string> | |
| 61 | + <string name="name">Name</string> | |
| 62 | + <string name="Quantity">Quantity</string> | |
| 63 | + <string name="price">Price</string> | |
| 64 | + <string name="total_amount">Total amount:</string> | |
| 65 | + <string name="cashier">Cashier:</string> | |
| 66 | + <string name="gap">label gap:</string> | |
| 67 | + <string-array name="gap"> | |
| 68 | + <item>0</item> | |
| 69 | + <item>1</item> | |
| 70 | + <item>2</item> | |
| 71 | + <item>3</item> | |
| 72 | + <item>4</item> | |
| 73 | + </string-array> | |
| 74 | +</resources> | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/values/styles.xml
0 → 100755
| 1 | +<resources> | |
| 2 | + | |
| 3 | + <!-- Base application theme. --> | |
| 4 | + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> | |
| 5 | + <!-- Customize your theme here. --> | |
| 6 | + <item name="colorPrimary">@color/colorPrimary</item> | |
| 7 | + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> | |
| 8 | + <item name="colorAccent">@color/colorAccent</item> | |
| 9 | + </style> | |
| 10 | + | |
| 11 | +</resources> | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/test/java/com/printer/tscdemo/ExampleUnitTest.java
0 → 100755
| 1 | +package com.printer.tscdemo; | |
| 2 | + | |
| 3 | +import org.junit.Test; | |
| 4 | + | |
| 5 | +import static org.junit.Assert.*; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * Example local unit test, which will execute on the development machine (host). | |
| 9 | + * | |
| 10 | + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | |
| 11 | + */ | |
| 12 | +public class ExampleUnitTest { | |
| 13 | + @Test | |
| 14 | + public void addition_isCorrect() { | |
| 15 | + assertEquals(4, 2 + 2); | |
| 16 | + } | |
| 17 | +} | |
| 0 | 18 | \ No newline at end of file | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/build.gradle
0 → 100755
| 1 | +// Top-level build file where you can add configuration options common to all sub-projects/modules. | |
| 2 | + | |
| 3 | +buildscript { | |
| 4 | + repositories { | |
| 5 | + mavenLocal() | |
| 6 | + mavenCentral() | |
| 7 | + //佳博SDK仓库 | |
| 8 | + maven { | |
| 9 | + url "http://118.31.6.84:8081/repository/maven-public/" | |
| 10 | + allowInsecureProtocol true | |
| 11 | + } | |
| 12 | + maven { | |
| 13 | + url 'https://maven.aliyun.com/nexus/content/groups/public/' | |
| 14 | + } | |
| 15 | + maven { | |
| 16 | + url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' | |
| 17 | + } | |
| 18 | + maven { | |
| 19 | + url 'https://maven.aliyun.com/nexus/content/repositories/google' | |
| 20 | + } | |
| 21 | + maven { | |
| 22 | + url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin' | |
| 23 | + } | |
| 24 | + | |
| 25 | + | |
| 26 | + } | |
| 27 | + dependencies { | |
| 28 | + classpath 'com.android.tools.build:gradle:4.2.1' | |
| 29 | + | |
| 30 | + // NOTE: Do not place your application dependencies here; they belong | |
| 31 | + // in the individual module build.gradle files | |
| 32 | + } | |
| 33 | +} | |
| 34 | + | |
| 35 | +allprojects { | |
| 36 | + repositories { | |
| 37 | + mavenLocal() | |
| 38 | + mavenCentral() | |
| 39 | +// //佳博SDK仓库 | |
| 40 | + maven { | |
| 41 | + url "http://118.31.6.84:8081/repository/maven-public/" | |
| 42 | + allowInsecureProtocol true | |
| 43 | + } | |
| 44 | + maven { | |
| 45 | + url 'https://maven.aliyun.com/nexus/content/groups/public/' | |
| 46 | + } | |
| 47 | + maven { | |
| 48 | + url 'https://maven.aliyun.com/nexus/content/repositories/jcenter' | |
| 49 | + } | |
| 50 | + maven { | |
| 51 | + url 'https://maven.aliyun.com/nexus/content/repositories/google' | |
| 52 | + } | |
| 53 | + maven { | |
| 54 | + url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin' | |
| 55 | + } | |
| 56 | + | |
| 57 | + | |
| 58 | + } | |
| 59 | +} | |
| 60 | + | |
| 61 | +task clean(type: Delete) { | |
| 62 | + delete rootProject.buildDir | |
| 63 | +} | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/gradle.properties
0 → 100755
| 1 | +# Project-wide Gradle settings. | |
| 2 | +# IDE (e.g. Android Studio) users: | |
| 3 | +# Gradle settings configured through the IDE *will override* | |
| 4 | +# any settings specified in this file. | |
| 5 | +# For more details on how to configure your build environment visit | |
| 6 | +# http://www.gradle.org/docs/current/userguide/build_environment.html | |
| 7 | +# Specifies the JVM arguments used for the daemon process. | |
| 8 | +# The setting is particularly useful for tweaking memory settings. | |
| 9 | +org.gradle.jvmargs=-Xmx1536m | |
| 10 | +# When configured, Gradle will run in incubating parallel mode. | |
| 11 | +# This option should only be used with decoupled projects. More details, visit | |
| 12 | +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | |
| 13 | +# org.gradle.parallel=true | |
| 14 | +# AndroidX package structure to make it clearer which packages are bundled with the | |
| 15 | +# Android operating system, and which are packaged with your app's APK | |
| 16 | +# https://developer.android.com/topic/libraries/support-library/androidx-rn | |
| 17 | +android.useAndroidX=true | |
| 18 | +# Automatically convert third-party libraries to use AndroidX | |
| 19 | +android.enableJetifier=true | |
| 20 | +android.injected.testOnly=false | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/gradle/wrapper/gradle-wrapper.jar
0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/gradle/wrapper/gradle-wrapper.properties
0 → 100755
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/gradlew
0 → 100755
| 1 | +#!/usr/bin/env sh | |
| 2 | + | |
| 3 | +############################################################################## | |
| 4 | +## | |
| 5 | +## Gradle start up script for UN*X | |
| 6 | +## | |
| 7 | +############################################################################## | |
| 8 | + | |
| 9 | +# Attempt to set APP_HOME | |
| 10 | +# Resolve links: $0 may be a link | |
| 11 | +PRG="$0" | |
| 12 | +# Need this for relative symlinks. | |
| 13 | +while [ -h "$PRG" ] ; do | |
| 14 | + ls=`ls -ld "$PRG"` | |
| 15 | + link=`expr "$ls" : '.*-> \(.*\)$'` | |
| 16 | + if expr "$link" : '/.*' > /dev/null; then | |
| 17 | + PRG="$link" | |
| 18 | + else | |
| 19 | + PRG=`dirname "$PRG"`"/$link" | |
| 20 | + fi | |
| 21 | +done | |
| 22 | +SAVED="`pwd`" | |
| 23 | +cd "`dirname \"$PRG\"`/" >/dev/null | |
| 24 | +APP_HOME="`pwd -P`" | |
| 25 | +cd "$SAVED" >/dev/null | |
| 26 | + | |
| 27 | +APP_NAME="Gradle" | |
| 28 | +APP_BASE_NAME=`basename "$0"` | |
| 29 | + | |
| 30 | +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |
| 31 | +DEFAULT_JVM_OPTS="" | |
| 32 | + | |
| 33 | +# Use the maximum available, or set MAX_FD != -1 to use that value. | |
| 34 | +MAX_FD="maximum" | |
| 35 | + | |
| 36 | +warn () { | |
| 37 | + echo "$*" | |
| 38 | +} | |
| 39 | + | |
| 40 | +die () { | |
| 41 | + echo | |
| 42 | + echo "$*" | |
| 43 | + echo | |
| 44 | + exit 1 | |
| 45 | +} | |
| 46 | + | |
| 47 | +# OS specific support (must be 'true' or 'false'). | |
| 48 | +cygwin=false | |
| 49 | +msys=false | |
| 50 | +darwin=false | |
| 51 | +nonstop=false | |
| 52 | +case "`uname`" in | |
| 53 | + CYGWIN* ) | |
| 54 | + cygwin=true | |
| 55 | + ;; | |
| 56 | + Darwin* ) | |
| 57 | + darwin=true | |
| 58 | + ;; | |
| 59 | + MINGW* ) | |
| 60 | + msys=true | |
| 61 | + ;; | |
| 62 | + NONSTOP* ) | |
| 63 | + nonstop=true | |
| 64 | + ;; | |
| 65 | +esac | |
| 66 | + | |
| 67 | +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | |
| 68 | + | |
| 69 | +# Determine the Java command to use to start the JVM. | |
| 70 | +if [ -n "$JAVA_HOME" ] ; then | |
| 71 | + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | |
| 72 | + # IBM's JDK on AIX uses strange locations for the executables | |
| 73 | + JAVACMD="$JAVA_HOME/jre/sh/java" | |
| 74 | + else | |
| 75 | + JAVACMD="$JAVA_HOME/bin/java" | |
| 76 | + fi | |
| 77 | + if [ ! -x "$JAVACMD" ] ; then | |
| 78 | + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | |
| 79 | + | |
| 80 | +Please set the JAVA_HOME variable in your environment to match the | |
| 81 | +location of your Java installation." | |
| 82 | + fi | |
| 83 | +else | |
| 84 | + JAVACMD="java" | |
| 85 | + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |
| 86 | + | |
| 87 | +Please set the JAVA_HOME variable in your environment to match the | |
| 88 | +location of your Java installation." | |
| 89 | +fi | |
| 90 | + | |
| 91 | +# Increase the maximum file descriptors if we can. | |
| 92 | +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then | |
| 93 | + MAX_FD_LIMIT=`ulimit -H -n` | |
| 94 | + if [ $? -eq 0 ] ; then | |
| 95 | + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | |
| 96 | + MAX_FD="$MAX_FD_LIMIT" | |
| 97 | + fi | |
| 98 | + ulimit -n $MAX_FD | |
| 99 | + if [ $? -ne 0 ] ; then | |
| 100 | + warn "Could not set maximum file descriptor limit: $MAX_FD" | |
| 101 | + fi | |
| 102 | + else | |
| 103 | + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | |
| 104 | + fi | |
| 105 | +fi | |
| 106 | + | |
| 107 | +# For Darwin, add options to specify how the application appears in the dock | |
| 108 | +if $darwin; then | |
| 109 | + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | |
| 110 | +fi | |
| 111 | + | |
| 112 | +# For Cygwin, switch paths to Windows format before running java | |
| 113 | +if $cygwin ; then | |
| 114 | + APP_HOME=`cygpath --path --mixed "$APP_HOME"` | |
| 115 | + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | |
| 116 | + JAVACMD=`cygpath --unix "$JAVACMD"` | |
| 117 | + | |
| 118 | + # We build the pattern for arguments to be converted via cygpath | |
| 119 | + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | |
| 120 | + SEP="" | |
| 121 | + for dir in $ROOTDIRSRAW ; do | |
| 122 | + ROOTDIRS="$ROOTDIRS$SEP$dir" | |
| 123 | + SEP="|" | |
| 124 | + done | |
| 125 | + OURCYGPATTERN="(^($ROOTDIRS))" | |
| 126 | + # Add a user-defined pattern to the cygpath arguments | |
| 127 | + if [ "$GRADLE_CYGPATTERN" != "" ] ; then | |
| 128 | + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | |
| 129 | + fi | |
| 130 | + # Now convert the arguments - kludge to limit ourselves to /bin/sh | |
| 131 | + i=0 | |
| 132 | + for arg in "$@" ; do | |
| 133 | + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | |
| 134 | + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option | |
| 135 | + | |
| 136 | + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition | |
| 137 | + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | |
| 138 | + else | |
| 139 | + eval `echo args$i`="\"$arg\"" | |
| 140 | + fi | |
| 141 | + i=$((i+1)) | |
| 142 | + done | |
| 143 | + case $i in | |
| 144 | + (0) set -- ;; | |
| 145 | + (1) set -- "$args0" ;; | |
| 146 | + (2) set -- "$args0" "$args1" ;; | |
| 147 | + (3) set -- "$args0" "$args1" "$args2" ;; | |
| 148 | + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; | |
| 149 | + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | |
| 150 | + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | |
| 151 | + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | |
| 152 | + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | |
| 153 | + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | |
| 154 | + esac | |
| 155 | +fi | |
| 156 | + | |
| 157 | +# Escape application args | |
| 158 | +save () { | |
| 159 | + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done | |
| 160 | + echo " " | |
| 161 | +} | |
| 162 | +APP_ARGS=$(save "$@") | |
| 163 | + | |
| 164 | +# Collect all arguments for the java command, following the shell quoting and substitution rules | |
| 165 | +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" | |
| 166 | + | |
| 167 | +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong | |
| 168 | +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then | |
| 169 | + cd "$(dirname "$0")" | |
| 170 | +fi | |
| 171 | + | |
| 172 | +exec "$JAVACMD" "$@" | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/gradlew.bat
0 → 100755
| 1 | +@if "%DEBUG%" == "" @echo off | |
| 2 | +@rem ########################################################################## | |
| 3 | +@rem | |
| 4 | +@rem Gradle startup script for Windows | |
| 5 | +@rem | |
| 6 | +@rem ########################################################################## | |
| 7 | + | |
| 8 | +@rem Set local scope for the variables with windows NT shell | |
| 9 | +if "%OS%"=="Windows_NT" setlocal | |
| 10 | + | |
| 11 | +set DIRNAME=%~dp0 | |
| 12 | +if "%DIRNAME%" == "" set DIRNAME=. | |
| 13 | +set APP_BASE_NAME=%~n0 | |
| 14 | +set APP_HOME=%DIRNAME% | |
| 15 | + | |
| 16 | +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | |
| 17 | +set DEFAULT_JVM_OPTS= | |
| 18 | + | |
| 19 | +@rem Find java.exe | |
| 20 | +if defined JAVA_HOME goto findJavaFromJavaHome | |
| 21 | + | |
| 22 | +set JAVA_EXE=java.exe | |
| 23 | +%JAVA_EXE% -version >NUL 2>&1 | |
| 24 | +if "%ERRORLEVEL%" == "0" goto init | |
| 25 | + | |
| 26 | +echo. | |
| 27 | +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | |
| 28 | +echo. | |
| 29 | +echo Please set the JAVA_HOME variable in your environment to match the | |
| 30 | +echo location of your Java installation. | |
| 31 | + | |
| 32 | +goto fail | |
| 33 | + | |
| 34 | +:findJavaFromJavaHome | |
| 35 | +set JAVA_HOME=%JAVA_HOME:"=% | |
| 36 | +set JAVA_EXE=%JAVA_HOME%/bin/java.exe | |
| 37 | + | |
| 38 | +if exist "%JAVA_EXE%" goto init | |
| 39 | + | |
| 40 | +echo. | |
| 41 | +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% | |
| 42 | +echo. | |
| 43 | +echo Please set the JAVA_HOME variable in your environment to match the | |
| 44 | +echo location of your Java installation. | |
| 45 | + | |
| 46 | +goto fail | |
| 47 | + | |
| 48 | +:init | |
| 49 | +@rem Get command-line arguments, handling Windows variants | |
| 50 | + | |
| 51 | +if not "%OS%" == "Windows_NT" goto win9xME_args | |
| 52 | + | |
| 53 | +:win9xME_args | |
| 54 | +@rem Slurp the command line arguments. | |
| 55 | +set CMD_LINE_ARGS= | |
| 56 | +set _SKIP=2 | |
| 57 | + | |
| 58 | +:win9xME_args_slurp | |
| 59 | +if "x%~1" == "x" goto execute | |
| 60 | + | |
| 61 | +set CMD_LINE_ARGS=%* | |
| 62 | + | |
| 63 | +:execute | |
| 64 | +@rem Setup the command line | |
| 65 | + | |
| 66 | +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | |
| 67 | + | |
| 68 | +@rem Execute Gradle | |
| 69 | +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% | |
| 70 | + | |
| 71 | +:end | |
| 72 | +@rem End local scope for the variables with windows NT shell | |
| 73 | +if "%ERRORLEVEL%"=="0" goto mainEnd | |
| 74 | + | |
| 75 | +:fail | |
| 76 | +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of | |
| 77 | +rem the _cmd.exe /c_ return code! | |
| 78 | +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 | |
| 79 | +exit /b 1 | |
| 80 | + | |
| 81 | +:mainEnd | |
| 82 | +if "%OS%"=="Windows_NT" endlocal | |
| 83 | + | |
| 84 | +:omega | ... | ... |
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/settings.gradle
0 → 100755
打印机安卓基座/README.md
0 → 100644
| 1 | +# 打印机安卓基座 | |
| 2 | + | |
| 3 | +这里只保留当前生效的安卓原生标签打印基座源码: | |
| 4 | + | |
| 5 | +- `native-fast-printer/` | |
| 6 | + | |
| 7 | +说明: | |
| 8 | + | |
| 9 | +- 这里是 `native-fast-printer` 的**唯一源码入口** | |
| 10 | +- `美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/` 只是给 uni-app 打包用的镜像目录 | |
| 11 | +- 修改原生代码后,先执行: | |
| 12 | + - `native-fast-printer/android-src/build-aar.sh` | |
| 13 | +- 再执行: | |
| 14 | + - `native-fast-printer/sync-to-uniapp.sh` | |
| 15 | + | |
| 16 | +当前目录只放这套原生打印基座代码,不再混放其他 SDK、参考项目或历史实验代码。 | ... | ... |
打印机安卓基座/native-fast-printer/README.md
0 → 100644
| 1 | +# native-fast-printer | |
| 2 | + | |
| 3 | +传统 `nativeplugins` Android 原生插件版高速标签打印模块。 | |
| 4 | + | |
| 5 | +## 能力 | |
| 6 | +- 经典蓝牙连接 / 断开 / 状态 | |
| 7 | +- 接收系统模板 JSON | |
| 8 | +- 原生生成 TSC 指令 | |
| 9 | +- 文本、价格、条码、二维码、横线、图片 | |
| 10 | +- 特殊字符文本和图片走原生位图补丁 | |
| 11 | + | |
| 12 | +## 前端调用 | |
| 13 | +```js | |
| 14 | +const printer = uni.requireNativePlugin('native-fast-printer') | |
| 15 | +``` | |
| 16 | + | |
| 17 | +## 方法 | |
| 18 | +- `connect(params, callback)` | |
| 19 | +- `disconnect(callback)` | |
| 20 | +- `isConnected(callback)` | |
| 21 | +- `printTemplate(params, callback)` | |
| 22 | + | |
| 23 | +## 源码位置 | |
| 24 | +- 当前目录是源码主目录 | |
| 25 | +- `美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/` 是同步后的 uni-app 打包镜像 | |
| 26 | + | |
| 27 | +## 目录结构 | |
| 28 | +- `android-src/src/com/foodlabel/nativeprinter/` | |
| 29 | + - `NativeFastPrinterModule.java`:uni-app 原生模块入口 | |
| 30 | + - `transport/`:蓝牙连接与 SDK 传输层 | |
| 31 | + - `template/`:系统模板 JSON → TSC 指令 | |
| 32 | + - `debug/`:调试状态与统计信息 | |
| 33 | + - `support/`:结果对象、JSON 读取、异常展开 | |
| 34 | +- `android/`:编译产物 AAR | |
| 35 | +- `sync-to-uniapp.sh`:同步到 uni-app 打包镜像 | |
| 36 | + | |
| 37 | +## 说明 | |
| 38 | +1. 修改源码后执行 `android-src/build-aar.sh` | |
| 39 | +2. 再执行 `sync-to-uniapp.sh` | |
| 40 | +3. 重新打包 uni-app 自定义基座 | ... | ... |
打印机安卓基座/native-fast-printer/android-src/build-aar.sh
0 → 100755
| 1 | +#!/bin/bash | |
| 2 | +set -euo pipefail | |
| 3 | + | |
| 4 | +ROOT_DIR="$(cd "$(dirname "$0")" && pwd)" | |
| 5 | +PROJECT_ROOT="$(cd "$ROOT_DIR/../../.." && pwd)" | |
| 6 | +OUT_DIR="$(cd "$ROOT_DIR/.." && pwd)/android" | |
| 7 | +SRC_DIR="$ROOT_DIR/src" | |
| 8 | +BUILD_DIR="$ROOT_DIR/build" | |
| 9 | +STUB_DIR="$BUILD_DIR/stubs" | |
| 10 | +CLASS_DIR="$BUILD_DIR/classes" | |
| 11 | +STUB_CLASS_DIR="$BUILD_DIR/stub-classes" | |
| 12 | +ANDROID_JAR="${ANDROID_JAR:-$HOME/Library/Android/sdk/platforms/android-34/android.jar}" | |
| 13 | +SDK_LIB_JAR="${SDK_LIB_JAR:-$PROJECT_ROOT/打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/libs/SDKLib.jar}" | |
| 14 | +OUT_AAR="$OUT_DIR/native_fast_printer-release.aar" | |
| 15 | + | |
| 16 | +rm -rf "$BUILD_DIR" | |
| 17 | +mkdir -p "$STUB_DIR/com/taobao/weex/annotation" \ | |
| 18 | + "$STUB_DIR/com/taobao/weex/bridge" \ | |
| 19 | + "$STUB_DIR/io/dcloud/feature/uniapp/common" \ | |
| 20 | + "$STUB_DIR/com/alibaba/fastjson" \ | |
| 21 | + "$CLASS_DIR" "$STUB_CLASS_DIR" "$OUT_DIR" "$BUILD_DIR/libs" | |
| 22 | + | |
| 23 | +cat > "$STUB_DIR/com/taobao/weex/annotation/JSMethod.java" <<'STUB' | |
| 24 | +package com.taobao.weex.annotation; | |
| 25 | +import java.lang.annotation.ElementType; | |
| 26 | +import java.lang.annotation.Retention; | |
| 27 | +import java.lang.annotation.RetentionPolicy; | |
| 28 | +import java.lang.annotation.Target; | |
| 29 | +@Retention(RetentionPolicy.RUNTIME) | |
| 30 | +@Target(ElementType.METHOD) | |
| 31 | +public @interface JSMethod { boolean uiThread() default true; } | |
| 32 | +STUB | |
| 33 | + | |
| 34 | +cat > "$STUB_DIR/com/taobao/weex/bridge/JSCallback.java" <<'STUB' | |
| 35 | +package com.taobao.weex.bridge; | |
| 36 | +public interface JSCallback { | |
| 37 | + void invoke(Object value); | |
| 38 | + void invokeAndKeepAlive(Object value); | |
| 39 | +} | |
| 40 | +STUB | |
| 41 | + | |
| 42 | +cat > "$STUB_DIR/io/dcloud/feature/uniapp/common/UniModule.java" <<'STUB' | |
| 43 | +package io.dcloud.feature.uniapp.common; | |
| 44 | +public class UniModule {} | |
| 45 | +STUB | |
| 46 | + | |
| 47 | +cat > "$STUB_DIR/com/alibaba/fastjson/JSONObject.java" <<'STUB' | |
| 48 | +package com.alibaba.fastjson; | |
| 49 | +import java.util.HashMap; | |
| 50 | +public class JSONObject extends HashMap<String, Object> { | |
| 51 | + public String getString(String key){ Object v = get(key); return v == null ? null : String.valueOf(v); } | |
| 52 | + public Integer getInteger(String key){ Object v = get(key); if (v == null) return null; if (v instanceof Number) return ((Number)v).intValue(); return Integer.parseInt(String.valueOf(v)); } | |
| 53 | +} | |
| 54 | +STUB | |
| 55 | + | |
| 56 | +find "$STUB_DIR" -name '*.java' -print0 | xargs -0 javac -source 1.8 -target 1.8 -encoding UTF-8 -cp "$ANDROID_JAR" -d "$STUB_CLASS_DIR" | |
| 57 | +jar cf "$BUILD_DIR/stubs.jar" -C "$STUB_CLASS_DIR" . | |
| 58 | + | |
| 59 | +find "$SRC_DIR" -name '*.java' -print0 | xargs -0 javac -source 1.8 -target 1.8 -encoding UTF-8 -cp "$ANDROID_JAR:$BUILD_DIR/stubs.jar:$SDK_LIB_JAR" -d "$CLASS_DIR" | |
| 60 | +jar cf "$BUILD_DIR/classes.jar" -C "$CLASS_DIR" . | |
| 61 | +cp "$SDK_LIB_JAR" "$BUILD_DIR/libs/SDKLib.jar" | |
| 62 | + | |
| 63 | +cat > "$BUILD_DIR/AndroidManifest.xml" <<'MANIFEST' | |
| 64 | +<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
| 65 | + package="com.foodlabel.nativeprinter" /> | |
| 66 | +MANIFEST | |
| 67 | +: > "$BUILD_DIR/R.txt" | |
| 68 | +mkdir -p "$BUILD_DIR/META-INF/com/android/build/gradle" | |
| 69 | +cat > "$BUILD_DIR/META-INF/com/android/build/gradle/aar-metadata.properties" <<'META' | |
| 70 | +aarFormatVersion=1.0 | |
| 71 | +aarMetadataVersion=1.0 | |
| 72 | +META | |
| 73 | + | |
| 74 | +cd "$BUILD_DIR" | |
| 75 | +rm -f "$OUT_AAR" | |
| 76 | +zip -q -r "$OUT_AAR" AndroidManifest.xml classes.jar R.txt META-INF libs | |
| 77 | + | |
| 78 | +echo "Built AAR: $OUT_AAR" | ... | ... |
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/NativeFastPrinterModule.java
0 → 100644
| 1 | +package com.foodlabel.nativeprinter; | |
| 2 | + | |
| 3 | +import com.alibaba.fastjson.JSONObject; | |
| 4 | +import com.foodlabel.nativeprinter.debug.NativePrintDebugState; | |
| 5 | +import com.foodlabel.nativeprinter.support.PluginResult; | |
| 6 | +import com.foodlabel.nativeprinter.support.SafeJson; | |
| 7 | +import com.foodlabel.nativeprinter.support.ThrowableUtils; | |
| 8 | +import com.foodlabel.nativeprinter.template.NativeTemplateCommandBuilder; | |
| 9 | +import com.foodlabel.nativeprinter.transport.GprinterBluetoothTransport; | |
| 10 | +import com.taobao.weex.annotation.JSMethod; | |
| 11 | +import com.taobao.weex.bridge.JSCallback; | |
| 12 | + | |
| 13 | +import java.util.concurrent.ExecutorService; | |
| 14 | +import java.util.concurrent.Executors; | |
| 15 | + | |
| 16 | +import io.dcloud.feature.uniapp.common.UniModule; | |
| 17 | + | |
| 18 | +public class NativeFastPrinterModule extends UniModule { | |
| 19 | + private static final String BACKEND = "gprinter-sdk"; | |
| 20 | + private static final String PLUGIN_VERSION = "1.1.0"; | |
| 21 | + private static final Object LOCK = new Object(); | |
| 22 | + private static final ExecutorService PRINT_EXECUTOR = Executors.newSingleThreadExecutor(); | |
| 23 | + private static final NativePrintDebugState DEBUG_STATE = new NativePrintDebugState(BACKEND, PLUGIN_VERSION); | |
| 24 | + private static final GprinterBluetoothTransport BLUETOOTH_TRANSPORT = new GprinterBluetoothTransport(); | |
| 25 | + | |
| 26 | + @JSMethod(uiThread = false) | |
| 27 | + public void connect(JSONObject params, JSCallback callback) { | |
| 28 | + String deviceId = SafeJson.getString(params, "deviceId", ""); | |
| 29 | + String deviceName = SafeJson.getString(params, "deviceName", ""); | |
| 30 | + PluginResult result = ensureConnected(deviceId, deviceName); | |
| 31 | + if (callback != null) { | |
| 32 | + callback.invoke(result.toJsonString()); | |
| 33 | + } | |
| 34 | + } | |
| 35 | + | |
| 36 | + @JSMethod(uiThread = false) | |
| 37 | + public void disconnect(JSCallback callback) { | |
| 38 | + synchronized (LOCK) { | |
| 39 | + BLUETOOTH_TRANSPORT.disconnect(); | |
| 40 | + DEBUG_STATE.clearCurrentDevice(); | |
| 41 | + DEBUG_STATE.setStage("disconnect:ok"); | |
| 42 | + DEBUG_STATE.clearError(); | |
| 43 | + } | |
| 44 | + if (callback != null) { | |
| 45 | + callback.invoke(debugResult(PluginResult.ok(false, "", "", "disconnect:ok")).toJsonString()); | |
| 46 | + } | |
| 47 | + } | |
| 48 | + | |
| 49 | + @JSMethod(uiThread = false) | |
| 50 | + public void isConnected(JSCallback callback) { | |
| 51 | + boolean connected; | |
| 52 | + synchronized (LOCK) { | |
| 53 | + connected = BLUETOOTH_TRANSPORT.isConnected(); | |
| 54 | + } | |
| 55 | + if (callback != null) { | |
| 56 | + callback.invoke(debugResult(PluginResult.ok(connected, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "isConnected:ok")).toJsonString()); | |
| 57 | + } | |
| 58 | + } | |
| 59 | + | |
| 60 | + @JSMethod(uiThread = false) | |
| 61 | + public void getDebugInfo(JSCallback callback) { | |
| 62 | + boolean connected; | |
| 63 | + synchronized (LOCK) { | |
| 64 | + connected = BLUETOOTH_TRANSPORT.isConnected(); | |
| 65 | + } | |
| 66 | + if (callback != null) { | |
| 67 | + callback.invoke(debugResult(PluginResult.ok(connected, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "debug:ok")).toJsonString()); | |
| 68 | + } | |
| 69 | + } | |
| 70 | + | |
| 71 | + @JSMethod(uiThread = false) | |
| 72 | + public void printTemplate(JSONObject params, JSCallback callback) { | |
| 73 | + String deviceId = SafeJson.getString(params, "deviceId", ""); | |
| 74 | + String deviceName = SafeJson.getString(params, "deviceName", ""); | |
| 75 | + String templateJson = SafeJson.getString(params, "templateJson", ""); | |
| 76 | + String dataJson = SafeJson.getString(params, "dataJson", "{}"); | |
| 77 | + int dpi = SafeJson.getInt(params, "dpi", 203); | |
| 78 | + int printQty = Math.max(1, SafeJson.getInt(params, "printQty", 1)); | |
| 79 | + | |
| 80 | + if (templateJson == null || templateJson.trim().isEmpty()) { | |
| 81 | + if (callback != null) { | |
| 82 | + callback.invoke(errorResult(9011006, "Template json is empty.").toJsonString()); | |
| 83 | + } | |
| 84 | + return; | |
| 85 | + } | |
| 86 | + | |
| 87 | + PluginResult connectResult = ensureConnected(deviceId, deviceName); | |
| 88 | + if (!connectResult.success) { | |
| 89 | + if (callback != null) callback.invoke(connectResult.toJsonString()); | |
| 90 | + return; | |
| 91 | + } | |
| 92 | + | |
| 93 | + DEBUG_STATE.setStage("printTemplate:queued"); | |
| 94 | + if (callback != null) { | |
| 95 | + callback.invoke(debugResult(PluginResult.ok(true, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "printTemplate:queued")).toJsonString()); | |
| 96 | + } | |
| 97 | + | |
| 98 | + PRINT_EXECUTOR.execute(new Runnable() { | |
| 99 | + @Override | |
| 100 | + public void run() { | |
| 101 | + try { | |
| 102 | + DEBUG_STATE.resetBuildMetrics(); | |
| 103 | + DEBUG_STATE.setStage("build-command"); | |
| 104 | + long buildStarted = System.currentTimeMillis(); | |
| 105 | + NativeTemplateCommandBuilder.BuildResult buildResult = | |
| 106 | + NativeTemplateCommandBuilder.buildWithStats(templateJson, dataJson, dpi, printQty); | |
| 107 | + DEBUG_STATE.setBuildMs(Math.max(0L, System.currentTimeMillis() - buildStarted)); | |
| 108 | + DEBUG_STATE.setBuildResult(buildResult); | |
| 109 | + | |
| 110 | + long writeStarted = System.currentTimeMillis(); | |
| 111 | + synchronized (LOCK) { | |
| 112 | + if (!BLUETOOTH_TRANSPORT.isConnected()) { | |
| 113 | + errorResult(9011005, "Bluetooth printer transport is not ready."); | |
| 114 | + return; | |
| 115 | + } | |
| 116 | + DEBUG_STATE.setStage("write-command"); | |
| 117 | + boolean ok = BLUETOOTH_TRANSPORT.write(buildResult.bytes); | |
| 118 | + if (!ok) { | |
| 119 | + errorResult(9011011, "Printer writeDataImmediately returned false."); | |
| 120 | + return; | |
| 121 | + } | |
| 122 | + } | |
| 123 | + DEBUG_STATE.setWriteMs(Math.max(0L, System.currentTimeMillis() - writeStarted)); | |
| 124 | + DEBUG_STATE.markPrinted(System.currentTimeMillis()); | |
| 125 | + DEBUG_STATE.setStage("printTemplate:ok"); | |
| 126 | + DEBUG_STATE.clearError(); | |
| 127 | + } catch (Throwable e) { | |
| 128 | + DEBUG_STATE.setError(ThrowableUtils.unwrap(e)); | |
| 129 | + DEBUG_STATE.setStage("printTemplate:error"); | |
| 130 | + } | |
| 131 | + } | |
| 132 | + }); | |
| 133 | + } | |
| 134 | + | |
| 135 | + private PluginResult ensureConnected(String deviceId, String deviceName) { | |
| 136 | + synchronized (LOCK) { | |
| 137 | + PluginResult result = BLUETOOTH_TRANSPORT.ensureConnected(deviceId, deviceName, DEBUG_STATE); | |
| 138 | + if (!result.success) { | |
| 139 | + return debugResult(result); | |
| 140 | + } | |
| 141 | + return debugResult(PluginResult.ok(true, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "connect:ok")); | |
| 142 | + } | |
| 143 | + } | |
| 144 | + | |
| 145 | + private static PluginResult errorResult(int code, String message) { | |
| 146 | + DEBUG_STATE.setError(message == null ? "" : message); | |
| 147 | + return debugResult(PluginResult.error(code, message)); | |
| 148 | + } | |
| 149 | + | |
| 150 | + private static PluginResult debugResult(PluginResult result) { | |
| 151 | + return DEBUG_STATE.attachTo(result); | |
| 152 | + } | |
| 153 | +} | ... | ... |
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/support/PluginResult.java
0 → 100644
| 1 | +package com.foodlabel.nativeprinter.support; | |
| 2 | + | |
| 3 | +import org.json.JSONObject; | |
| 4 | + | |
| 5 | +public final class PluginResult { | |
| 6 | + public final boolean success; | |
| 7 | + public final int code; | |
| 8 | + public final String message; | |
| 9 | + public final boolean connected; | |
| 10 | + public final String deviceId; | |
| 11 | + public final String deviceName; | |
| 12 | + public final JSONObject extra; | |
| 13 | + | |
| 14 | + private PluginResult(boolean success, int code, String message, boolean connected, String deviceId, String deviceName) { | |
| 15 | + this.success = success; | |
| 16 | + this.code = code; | |
| 17 | + this.message = message == null ? "" : message; | |
| 18 | + this.connected = connected; | |
| 19 | + this.deviceId = deviceId == null ? "" : deviceId; | |
| 20 | + this.deviceName = deviceName == null ? "" : deviceName; | |
| 21 | + this.extra = new JSONObject(); | |
| 22 | + } | |
| 23 | + | |
| 24 | + public static PluginResult ok(boolean connected, String deviceId, String deviceName, String message) { | |
| 25 | + return new PluginResult(true, 1, message, connected, deviceId, deviceName); | |
| 26 | + } | |
| 27 | + | |
| 28 | + public static PluginResult error(int code, String message) { | |
| 29 | + return new PluginResult(false, code, message, false, "", ""); | |
| 30 | + } | |
| 31 | + | |
| 32 | + public PluginResult withMeta(String key, Object value) { | |
| 33 | + if (key == null || key.isEmpty() || value == null) return this; | |
| 34 | + try { | |
| 35 | + extra.put(key, value); | |
| 36 | + } catch (Exception ignored) { | |
| 37 | + } | |
| 38 | + return this; | |
| 39 | + } | |
| 40 | + | |
| 41 | + public String toJsonString() { | |
| 42 | + try { | |
| 43 | + JSONObject json = new JSONObject(); | |
| 44 | + json.put("code", success ? 1 : code); | |
| 45 | + json.put("msg", message); | |
| 46 | + json.put("connected", connected); | |
| 47 | + json.put("deviceId", deviceId); | |
| 48 | + json.put("deviceName", deviceName); | |
| 49 | + json.put("success", success); | |
| 50 | + java.util.Iterator<String> keys = extra.keys(); | |
| 51 | + while (keys.hasNext()) { | |
| 52 | + String key = keys.next(); | |
| 53 | + json.put(key, extra.opt(key)); | |
| 54 | + } | |
| 55 | + return json.toString(); | |
| 56 | + } catch (Exception e) { | |
| 57 | + return "{\"code\":0,\"msg\":\"plugin_result_error\"}"; | |
| 58 | + } | |
| 59 | + } | |
| 60 | +} | ... | ... |
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/support/SafeJson.java
0 → 100644
| 1 | +package com.foodlabel.nativeprinter.support; | |
| 2 | + | |
| 3 | +import com.alibaba.fastjson.JSONObject; | |
| 4 | + | |
| 5 | +public final class SafeJson { | |
| 6 | + private SafeJson() {} | |
| 7 | + | |
| 8 | + public static String getString(JSONObject json, String key, String fallback) { | |
| 9 | + if (json == null || key == null) return fallback; | |
| 10 | + try { | |
| 11 | + String value = json.getString(key); | |
| 12 | + return value == null ? fallback : value; | |
| 13 | + } catch (Exception e) { | |
| 14 | + Object value = json.get(key); | |
| 15 | + return value == null ? fallback : String.valueOf(value); | |
| 16 | + } | |
| 17 | + } | |
| 18 | + | |
| 19 | + public static int getInt(JSONObject json, String key, int fallback) { | |
| 20 | + if (json == null || key == null) return fallback; | |
| 21 | + try { | |
| 22 | + Integer value = json.getInteger(key); | |
| 23 | + return value == null ? fallback : value; | |
| 24 | + } catch (Exception e) { | |
| 25 | + try { | |
| 26 | + Object value = json.get(key); | |
| 27 | + return value == null ? fallback : Integer.parseInt(String.valueOf(value)); | |
| 28 | + } catch (Exception ignored) { | |
| 29 | + return fallback; | |
| 30 | + } | |
| 31 | + } | |
| 32 | + } | |
| 33 | +} | ... | ... |
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/support/ThrowableUtils.java
0 → 100644
| 1 | +package com.foodlabel.nativeprinter.support; | |
| 2 | + | |
| 3 | +public final class ThrowableUtils { | |
| 4 | + private ThrowableUtils() { | |
| 5 | + } | |
| 6 | + | |
| 7 | + public static String unwrap(Throwable error) { | |
| 8 | + if (error == null) return "unknown_error"; | |
| 9 | + StringBuilder builder = new StringBuilder(); | |
| 10 | + Throwable current = error; | |
| 11 | + int depth = 0; | |
| 12 | + while (current != null && depth < 6) { | |
| 13 | + if (builder.length() > 0) builder.append(" | caused by: "); | |
| 14 | + builder.append(current.getClass().getName()); | |
| 15 | + String message = current.getMessage(); | |
| 16 | + if (message != null && !message.trim().isEmpty()) { | |
| 17 | + builder.append(": ").append(message); | |
| 18 | + } | |
| 19 | + current = current.getCause(); | |
| 20 | + depth++; | |
| 21 | + } | |
| 22 | + return builder.toString(); | |
| 23 | + } | |
| 24 | +} | ... | ... |
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/template/NativeTemplateCommandBuilder.java
0 → 100644
| 1 | +package com.foodlabel.nativeprinter.template; | |
| 2 | + | |
| 3 | +import android.graphics.Bitmap; | |
| 4 | +import android.graphics.BitmapFactory; | |
| 5 | +import android.graphics.Canvas; | |
| 6 | +import android.graphics.Color; | |
| 7 | +import android.graphics.Paint; | |
| 8 | +import android.graphics.Typeface; | |
| 9 | +import android.util.Base64; | |
| 10 | + | |
| 11 | +import org.json.JSONArray; | |
| 12 | +import org.json.JSONObject; | |
| 13 | + | |
| 14 | +import java.io.ByteArrayOutputStream; | |
| 15 | +import java.nio.charset.Charset; | |
| 16 | +import java.nio.charset.CharsetEncoder; | |
| 17 | +import java.nio.charset.StandardCharsets; | |
| 18 | +import java.util.regex.Matcher; | |
| 19 | +import java.util.regex.Pattern; | |
| 20 | + | |
| 21 | +public final class NativeTemplateCommandBuilder { | |
| 22 | + private static final double DESIGN_DPI = 96.0; | |
| 23 | + private static final int TEXT_PADDING_DOTS = 6; | |
| 24 | + private static final int DEFAULT_THRESHOLD = 180; | |
| 25 | + private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{\\s*([\\w.-]+)\\s*\\}\\}"); | |
| 26 | + | |
| 27 | + private NativeTemplateCommandBuilder() { | |
| 28 | + } | |
| 29 | + | |
| 30 | + public static byte[] build(String templateJson, String dataJson, int dpi, int printQty) throws Exception { | |
| 31 | + JSONObject template = new JSONObject(templateJson); | |
| 32 | + JSONObject data = (dataJson == null || dataJson.trim().isEmpty()) ? new JSONObject() : new JSONObject(dataJson); | |
| 33 | + return buildWithStats(template, data, dpi, printQty).bytes; | |
| 34 | + } | |
| 35 | + | |
| 36 | + public static byte[] build(JSONObject template, JSONObject data, int dpi, int printQty) throws Exception { | |
| 37 | + return buildWithStats(template, data, dpi, printQty).bytes; | |
| 38 | + } | |
| 39 | + | |
| 40 | + public static BuildResult buildWithStats(String templateJson, String dataJson, int dpi, int printQty) throws Exception { | |
| 41 | + JSONObject template = new JSONObject(templateJson); | |
| 42 | + JSONObject data = (dataJson == null || dataJson.trim().isEmpty()) ? new JSONObject() : new JSONObject(dataJson); | |
| 43 | + return buildWithStats(template, data, dpi, printQty); | |
| 44 | + } | |
| 45 | + | |
| 46 | + public static BuildResult buildWithStats(JSONObject template, JSONObject data, int dpi, int printQty) throws Exception { | |
| 47 | + ByteArrayOutputStream out = new ByteArrayOutputStream(); | |
| 48 | + int nativeTextCount = 0; | |
| 49 | + int rasterTextCount = 0; | |
| 50 | + int qrCodeCount = 0; | |
| 51 | + int barcodeCount = 0; | |
| 52 | + int imagePatchCount = 0; | |
| 53 | + int lineCount = 0; | |
| 54 | + String unit = getString(template, "unit", "inch"); | |
| 55 | + double widthMm = round1(toMillimeter(getDouble(template, "width", 0), unit)); | |
| 56 | + double heightMm = round1(toMillimeter(getDouble(template, "height", 0), unit)); | |
| 57 | + double pageWidthPx = widthMm / 25.4 * DESIGN_DPI; | |
| 58 | + | |
| 59 | + addLine(out, "SIZE " + formatMm(widthMm) + " mm," + formatMm(heightMm) + " mm"); | |
| 60 | + addLine(out, "GAP 0 mm,0 mm"); | |
| 61 | + addLine(out, "CODEPAGE 1252"); | |
| 62 | + addLine(out, "DENSITY 14"); | |
| 63 | + addLine(out, "SPEED 5"); | |
| 64 | + addLine(out, "CLS"); | |
| 65 | + | |
| 66 | + JSONArray elements = template.optJSONArray("elements"); | |
| 67 | + if (elements != null) { | |
| 68 | + for (int i = 0; i < elements.length(); i++) { | |
| 69 | + JSONObject element = elements.optJSONObject(i); | |
| 70 | + if (element == null) continue; | |
| 71 | + JSONObject config = element.optJSONObject("config"); | |
| 72 | + if (config == null) config = new JSONObject(); | |
| 73 | + String type = getString(element, "type", "").toUpperCase(); | |
| 74 | + | |
| 75 | + if (type.startsWith("TEXT_")) { | |
| 76 | + String text = resolveElementText(type, config, data); | |
| 77 | + if (text.isEmpty()) continue; | |
| 78 | + String align = resolveElementAlign(element, config, pageWidthPx); | |
| 79 | + if (shouldRasterizeText(text, type)) { | |
| 80 | + rasterTextCount++; | |
| 81 | + BitmapPatch patch = createTextPatch(element, type, config, text, dpi, align); | |
| 82 | + writeBitmapPatch(out, patch); | |
| 83 | + } else { | |
| 84 | + nativeTextCount++; | |
| 85 | + int scale = resolveTextScale(getDouble(config, "fontSize", 14), dpi); | |
| 86 | + int x = resolveTextX(align, getDouble(element, "x", 0), getDouble(element, "width", 0), dpi, text, scale); | |
| 87 | + int y = pxToDots(getDouble(element, "y", 0), dpi); | |
| 88 | + int rotation = "vertical".equalsIgnoreCase(getString(element, "rotation", "horizontal")) ? 90 : 0; | |
| 89 | + addLine(out, "TEXT " + x + "," + y + ",\"TSS24.BF2\"," + rotation + "," + scale + "," + scale + ",\"" + escapeTscString(text) + "\""); | |
| 90 | + } | |
| 91 | + continue; | |
| 92 | + } | |
| 93 | + | |
| 94 | + if ("QRCODE".equals(type)) { | |
| 95 | + qrCodeCount++; | |
| 96 | + String value = resolveElementDataValue(type, config, data); | |
| 97 | + if (value.isEmpty()) continue; | |
| 98 | + String level = normalizeQrLevel(getString(config, "errorLevel", "M")); | |
| 99 | + int x = pxToDots(getDouble(element, "x", 0), dpi); | |
| 100 | + int y = pxToDots(getDouble(element, "y", 0), dpi); | |
| 101 | + int size = resolveQrModuleSize(getDouble(element, "width", 0), getDouble(element, "height", 0), dpi, value, level); | |
| 102 | + addLine(out, "QRCODE " + x + "," + y + "," + level + "," + size + ",A,0,\"" + escapeTscString(value) + "\""); | |
| 103 | + continue; | |
| 104 | + } | |
| 105 | + | |
| 106 | + if ("BARCODE".equals(type)) { | |
| 107 | + barcodeCount++; | |
| 108 | + String value = resolveElementDataValue(type, config, data); | |
| 109 | + if (value.isEmpty()) continue; | |
| 110 | + int x = pxToDots(getDouble(element, "x", 0), dpi); | |
| 111 | + int y = pxToDots(getDouble(element, "y", 0), dpi); | |
| 112 | + int height = Math.max(20, pxToDots(getDouble(element, "height", 0), dpi)); | |
| 113 | + int readable = getBoolean(config, "showText", true) ? 1 : 0; | |
| 114 | + String orientation = getString(config, "orientation", getString(element, "rotation", "horizontal")); | |
| 115 | + int rotation = "vertical".equalsIgnoreCase(orientation) ? 90 : 0; | |
| 116 | + int narrow = clamp(getDouble(element, "width", 0) / Math.max(40.0, value.length() * 6.0), 1, 4); | |
| 117 | + int wide = clamp(getDouble(element, "width", 0) / Math.max(24.0, value.length() * 3.0), 2, 6); | |
| 118 | + String symbology = normalizeBarcodeType(getString(config, "barcodeType", "CODE128")); | |
| 119 | + addLine(out, "BARCODE " + x + "," + y + ",\"" + symbology + "\"," + height + "," + readable + "," + rotation + "," + narrow + "," + wide + ",\"" + escapeTscString(value) + "\""); | |
| 120 | + continue; | |
| 121 | + } | |
| 122 | + | |
| 123 | + if ("IMAGE".equals(type)) { | |
| 124 | + BitmapPatch patch = createImagePatch(element, config, dpi); | |
| 125 | + if (patch != null) { | |
| 126 | + imagePatchCount++; | |
| 127 | + writeBitmapPatch(out, patch); | |
| 128 | + } | |
| 129 | + continue; | |
| 130 | + } | |
| 131 | + | |
| 132 | + if ("BLANK".equals(type) && "line".equalsIgnoreCase(getString(element, "border", ""))) { | |
| 133 | + lineCount++; | |
| 134 | + int x = pxToDots(getDouble(element, "x", 0), dpi); | |
| 135 | + int y = pxToDots(getDouble(element, "y", 0), dpi); | |
| 136 | + int width = Math.max(1, pxToDots(getDouble(element, "width", 0), dpi)); | |
| 137 | + int height = Math.max(1, pxToDots(getDouble(element, "height", 1), dpi)); | |
| 138 | + addLine(out, "BAR " + x + "," + y + "," + width + "," + height); | |
| 139 | + } | |
| 140 | + } | |
| 141 | + } | |
| 142 | + | |
| 143 | + addLine(out, "PRINT 1," + Math.max(1, printQty)); | |
| 144 | + return new BuildResult( | |
| 145 | + out.toByteArray(), | |
| 146 | + nativeTextCount, | |
| 147 | + rasterTextCount, | |
| 148 | + qrCodeCount, | |
| 149 | + barcodeCount, | |
| 150 | + imagePatchCount, | |
| 151 | + lineCount, | |
| 152 | + elements == null ? 0 : elements.length() | |
| 153 | + ); | |
| 154 | + } | |
| 155 | + | |
| 156 | + private static String resolveElementText(String type, JSONObject config, JSONObject data) { | |
| 157 | + String configText = getString(config, "text", ""); | |
| 158 | + boolean hasText = !configText.isEmpty(); | |
| 159 | + if ("TEXT_PRICE".equals(type)) { | |
| 160 | + String bindingKey = resolveBindingKey(type, config); | |
| 161 | + String boundValue = resolveTemplateValue(data, bindingKey); | |
| 162 | + String raw = !boundValue.isEmpty() ? boundValue : (hasText ? applyTemplateData(configText, data) : ""); | |
| 163 | + if (raw.isEmpty()) return ""; | |
| 164 | + String prefix = getString(config, "prefix", ""); | |
| 165 | + String suffix = getString(config, "suffix", ""); | |
| 166 | + int decimal = (int) getDouble(config, "decimal", -1); | |
| 167 | + if (decimal >= 0) { | |
| 168 | + try { | |
| 169 | + double value = Double.parseDouble(raw); | |
| 170 | + raw = String.format(java.util.Locale.US, "%1$." + decimal + "f", value); | |
| 171 | + } catch (Exception ignored) { | |
| 172 | + } | |
| 173 | + } | |
| 174 | + return prefix + raw + suffix; | |
| 175 | + } | |
| 176 | + if (hasText && "TEXT_STATIC".equals(type)) { | |
| 177 | + return applyTemplateData(configText, data); | |
| 178 | + } | |
| 179 | + if (hasText && configText.contains("{{")) { | |
| 180 | + return applyTemplateData(configText, data); | |
| 181 | + } | |
| 182 | + String bindingKey = resolveBindingKey(type, config); | |
| 183 | + String boundValue = resolveTemplateValue(data, bindingKey); | |
| 184 | + if (!boundValue.isEmpty()) return boundValue; | |
| 185 | + return hasText ? applyTemplateData(configText, data) : ""; | |
| 186 | + } | |
| 187 | + | |
| 188 | + private static String resolveElementDataValue(String type, JSONObject config, JSONObject data) { | |
| 189 | + String raw = getString(config, "data", getString(config, "value", "")); | |
| 190 | + if (!raw.isEmpty()) return applyTemplateData(raw, data); | |
| 191 | + return resolveTemplateValue(data, resolveBindingKey(type, config)); | |
| 192 | + } | |
| 193 | + | |
| 194 | + private static String resolveBindingKey(String type, JSONObject config) { | |
| 195 | + String[] keys = new String[]{"dataKey", "field", "bindField", "key", "valueKey"}; | |
| 196 | + for (String key : keys) { | |
| 197 | + String value = getString(config, key, ""); | |
| 198 | + if (!value.isEmpty()) return value; | |
| 199 | + } | |
| 200 | + switch (type) { | |
| 201 | + case "TEXT_PRODUCT": return "productName"; | |
| 202 | + case "TEXT_LABEL_ID": return "labelId"; | |
| 203 | + case "TEXT_CATEGORY": return "category"; | |
| 204 | + case "TEXT_PRICE": return "price"; | |
| 205 | + case "TEXT_DATE": return "date"; | |
| 206 | + case "TEXT_TIME": return "time"; | |
| 207 | + case "QRCODE": return "qrCode"; | |
| 208 | + case "BARCODE": return "barcode"; | |
| 209 | + default: | |
| 210 | + String pureType = type.replace("TEXT_", "").replace("FIELD_", "").replace("VALUE_", ""); | |
| 211 | + return pureType.isEmpty() ? "" : toCamelCase(pureType); | |
| 212 | + } | |
| 213 | + } | |
| 214 | + | |
| 215 | + private static String resolveTemplateValue(JSONObject data, String key) { | |
| 216 | + if (key == null || key.isEmpty()) return ""; | |
| 217 | + String[] candidates; | |
| 218 | + switch (key) { | |
| 219 | + case "productName": candidates = new String[]{"productName", "product"}; break; | |
| 220 | + case "product": candidates = new String[]{"product", "productName"}; break; | |
| 221 | + case "qrCode": candidates = new String[]{"qrCode", "labelId", "barcode"}; break; | |
| 222 | + case "barcode": candidates = new String[]{"barcode", "labelId", "qrCode"}; break; | |
| 223 | + default: candidates = new String[]{key}; | |
| 224 | + } | |
| 225 | + for (String candidate : candidates) { | |
| 226 | + Object value = data.opt(candidate); | |
| 227 | + if (value != null) return String.valueOf(value); | |
| 228 | + } | |
| 229 | + return ""; | |
| 230 | + } | |
| 231 | + | |
| 232 | + private static String applyTemplateData(String text, JSONObject data) { | |
| 233 | + Matcher matcher = PLACEHOLDER_PATTERN.matcher(text == null ? "" : text); | |
| 234 | + StringBuffer buffer = new StringBuffer(); | |
| 235 | + while (matcher.find()) { | |
| 236 | + String key = matcher.group(1); | |
| 237 | + Object value = data.opt(key); | |
| 238 | + matcher.appendReplacement(buffer, Matcher.quoteReplacement(value == null ? "" : String.valueOf(value))); | |
| 239 | + } | |
| 240 | + matcher.appendTail(buffer); | |
| 241 | + return buffer.toString(); | |
| 242 | + } | |
| 243 | + | |
| 244 | + private static String toCamelCase(String value) { | |
| 245 | + String[] parts = value.toLowerCase().split("[_\\s-]+"); | |
| 246 | + StringBuilder builder = new StringBuilder(); | |
| 247 | + for (int i = 0; i < parts.length; i++) { | |
| 248 | + if (parts[i].isEmpty()) continue; | |
| 249 | + if (builder.length() == 0) { | |
| 250 | + builder.append(parts[i]); | |
| 251 | + } else { | |
| 252 | + builder.append(Character.toUpperCase(parts[i].charAt(0))).append(parts[i].substring(1)); | |
| 253 | + } | |
| 254 | + } | |
| 255 | + return builder.toString(); | |
| 256 | + } | |
| 257 | + | |
| 258 | + private static String resolveElementAlign(JSONObject element, JSONObject config, double pageWidthPx) { | |
| 259 | + String align = getString(config, "textAlign", "").toLowerCase(); | |
| 260 | + if ("left".equals(align) || "center".equals(align) || "right".equals(align)) return align; | |
| 261 | + double centerX = getDouble(element, "x", 0) + getDouble(element, "width", 0) / 2.0; | |
| 262 | + if (centerX <= pageWidthPx * 0.33) return "left"; | |
| 263 | + if (centerX >= pageWidthPx * 0.67) return "right"; | |
| 264 | + return "center"; | |
| 265 | + } | |
| 266 | + | |
| 267 | + private static boolean shouldRasterizeText(String text, String type) { | |
| 268 | + if (text == null || text.isEmpty()) return false; | |
| 269 | + for (int i = 0; i < text.length(); i++) { | |
| 270 | + char c = text.charAt(i); | |
| 271 | + if (c < 32 || c > 126) { | |
| 272 | + return true; | |
| 273 | + } | |
| 274 | + } | |
| 275 | + CharsetEncoder encoder = getPrinterEncoder(); | |
| 276 | + if (encoder == null) return true; | |
| 277 | + try { | |
| 278 | + return !encoder.canEncode(text); | |
| 279 | + } catch (Exception e) { | |
| 280 | + return true; | |
| 281 | + } | |
| 282 | + } | |
| 283 | + | |
| 284 | + private static BitmapPatch createTextPatch(JSONObject element, String type, JSONObject config, String text, int dpi, String align) { | |
| 285 | + int contentWidth = Math.max(8, pxToDots(getDouble(element, "width", 0), dpi)); | |
| 286 | + Paint paint = new Paint(); | |
| 287 | + paint.setAntiAlias(true); | |
| 288 | + paint.setDither(true); | |
| 289 | + paint.setSubpixelText(true); | |
| 290 | + paint.setColor(Color.BLACK); | |
| 291 | + int fontSizeDots = Math.max(14, pxToDots(getDouble(config, "fontSize", 14), dpi)); | |
| 292 | + paint.setTextSize(fontSizeDots); | |
| 293 | + boolean bold = "bold".equalsIgnoreCase(getString(config, "fontWeight", "")) || "TEXT_PRICE".equals(type); | |
| 294 | + paint.setFakeBoldText(bold); | |
| 295 | + paint.setTypeface(bold ? Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD) : Typeface.SANS_SERIF); | |
| 296 | + | |
| 297 | + if (text.indexOf('\n') < 0) { | |
| 298 | + int singleLineWidth = (int) Math.ceil(paint.measureText(text)) + 8; | |
| 299 | + contentWidth = Math.max(contentWidth, singleLineWidth); | |
| 300 | + } | |
| 301 | + | |
| 302 | + java.util.List<String> lines = splitTextLines(text, paint, Math.max(8, contentWidth)); | |
| 303 | + Paint.FontMetrics metrics = paint.getFontMetrics(); | |
| 304 | + int lineHeight = Math.max(fontSizeDots + 2, (int) Math.ceil(Math.abs(metrics.top) + Math.abs(metrics.bottom) + 2)); | |
| 305 | + int totalHeight = lines.size() * lineHeight; | |
| 306 | + float maxLineWidth = 0; | |
| 307 | + for (String line : lines) { | |
| 308 | + maxLineWidth = Math.max(maxLineWidth, paint.measureText(line)); | |
| 309 | + } | |
| 310 | + int horizontalPadding = TEXT_PADDING_DOTS * 2; | |
| 311 | + int verticalPadding = TEXT_PADDING_DOTS * 2; | |
| 312 | + int width = ensureMultipleOf8(Math.max(contentWidth + horizontalPadding * 2, (int) Math.ceil(maxLineWidth) + horizontalPadding * 2 + 4)); | |
| 313 | + int height = Math.max(16, Math.max(pxToDots(getDouble(element, "height", 0), dpi) + verticalPadding * 2, totalHeight + verticalPadding * 2 + 4)); | |
| 314 | + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); | |
| 315 | + Canvas canvas = new Canvas(bitmap); | |
| 316 | + canvas.drawColor(Color.WHITE); | |
| 317 | + | |
| 318 | + int topOffset = "TEXT_PRICE".equals(type) | |
| 319 | + ? Math.max(verticalPadding, (height - totalHeight) / 2) | |
| 320 | + : verticalPadding; | |
| 321 | + int drawableWidth = width - horizontalPadding * 2; | |
| 322 | + | |
| 323 | + for (int i = 0; i < lines.size(); i++) { | |
| 324 | + String line = lines.get(i); | |
| 325 | + float lineWidth = paint.measureText(line); | |
| 326 | + float drawX = horizontalPadding; | |
| 327 | + if ("center".equals(align)) { | |
| 328 | + drawX = horizontalPadding + Math.max(0, (drawableWidth - lineWidth) / 2f); | |
| 329 | + } else if ("right".equals(align)) { | |
| 330 | + drawX = horizontalPadding + Math.max(0, drawableWidth - lineWidth); | |
| 331 | + } | |
| 332 | + float baseline = topOffset + i * lineHeight - metrics.top; | |
| 333 | + canvas.drawText(line, drawX, baseline, paint); | |
| 334 | + } | |
| 335 | + | |
| 336 | + BitmapPatch patch = new BitmapPatch(Math.max(0, pxToDots(getDouble(element, "x", 0), dpi) - horizontalPadding), | |
| 337 | + Math.max(0, pxToDots(getDouble(element, "y", 0), dpi) - verticalPadding), | |
| 338 | + invertMonochrome(bitmapToMonochrome(bitmap, DEFAULT_THRESHOLD))); | |
| 339 | + bitmap.recycle(); | |
| 340 | + return patch; | |
| 341 | + } | |
| 342 | + | |
| 343 | + private static BitmapPatch createImagePatch(JSONObject element, JSONObject config, int dpi) { | |
| 344 | + String source = getString(config, "src", getString(config, "data", getString(config, "url", ""))); | |
| 345 | + if (source.isEmpty()) return null; | |
| 346 | + Bitmap sourceBitmap = decodeBitmap(source); | |
| 347 | + if (sourceBitmap == null) return null; | |
| 348 | + | |
| 349 | + int width = ensureMultipleOf8(Math.max(8, pxToDots(getDouble(element, "width", 0), dpi))); | |
| 350 | + int height = Math.max(8, pxToDots(getDouble(element, "height", 0), dpi)); | |
| 351 | + Bitmap outputBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); | |
| 352 | + Canvas canvas = new Canvas(outputBitmap); | |
| 353 | + canvas.drawColor(Color.WHITE); | |
| 354 | + | |
| 355 | + int sourceWidth = sourceBitmap.getWidth(); | |
| 356 | + int sourceHeight = sourceBitmap.getHeight(); | |
| 357 | + String scaleMode = getString(config, "scaleMode", "contain").toLowerCase(); | |
| 358 | + int targetWidth = width; | |
| 359 | + int targetHeight = height; | |
| 360 | + int targetLeft = 0; | |
| 361 | + int targetTop = 0; | |
| 362 | + | |
| 363 | + if (sourceWidth > 0 && sourceHeight > 0 && !"fill".equals(scaleMode)) { | |
| 364 | + double ratio = "cover".equals(scaleMode) | |
| 365 | + ? Math.max((double) width / sourceWidth, (double) height / sourceHeight) | |
| 366 | + : Math.min((double) width / sourceWidth, (double) height / sourceHeight); | |
| 367 | + targetWidth = Math.max(1, (int) Math.round(sourceWidth * ratio)); | |
| 368 | + targetHeight = Math.max(1, (int) Math.round(sourceHeight * ratio)); | |
| 369 | + targetLeft = (width - targetWidth) / 2; | |
| 370 | + targetTop = (height - targetHeight) / 2; | |
| 371 | + } | |
| 372 | + | |
| 373 | + Bitmap scaledBitmap = Bitmap.createScaledBitmap(sourceBitmap, targetWidth, targetHeight, true); | |
| 374 | + Paint paint = new Paint(); | |
| 375 | + paint.setAntiAlias(true); | |
| 376 | + paint.setFilterBitmap(true); | |
| 377 | + canvas.drawBitmap(scaledBitmap, targetLeft, targetTop, paint); | |
| 378 | + | |
| 379 | + BitmapPatch patch = new BitmapPatch(pxToDots(getDouble(element, "x", 0), dpi), | |
| 380 | + pxToDots(getDouble(element, "y", 0), dpi), | |
| 381 | + bitmapToMonochrome(outputBitmap, (int) getDouble(config, "threshold", DEFAULT_THRESHOLD))); | |
| 382 | + | |
| 383 | + scaledBitmap.recycle(); | |
| 384 | + sourceBitmap.recycle(); | |
| 385 | + outputBitmap.recycle(); | |
| 386 | + return patch; | |
| 387 | + } | |
| 388 | + | |
| 389 | + private static Bitmap decodeBitmap(String source) { | |
| 390 | + try { | |
| 391 | + if (source.startsWith("data:image/")) { | |
| 392 | + int comma = source.indexOf(','); | |
| 393 | + String payload = comma >= 0 ? source.substring(comma + 1) : ""; | |
| 394 | + if (payload.isEmpty()) return null; | |
| 395 | + byte[] bytes = Base64.decode(payload, Base64.DEFAULT); | |
| 396 | + return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); | |
| 397 | + } | |
| 398 | + if (source.matches("^[A-Za-z0-9+/=\\r\\n]+$") && source.length() > 128) { | |
| 399 | + byte[] bytes = Base64.decode(source, Base64.DEFAULT); | |
| 400 | + return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); | |
| 401 | + } | |
| 402 | + String path = source.startsWith("file://") ? source.substring(7) : source; | |
| 403 | + return BitmapFactory.decodeFile(path); | |
| 404 | + } catch (Exception e) { | |
| 405 | + return null; | |
| 406 | + } | |
| 407 | + } | |
| 408 | + | |
| 409 | + private static java.util.List<String> splitTextLines(String text, Paint paint, int maxWidth) { | |
| 410 | + java.util.List<String> lines = new java.util.ArrayList<>(); | |
| 411 | + String[] rawLines = (text == null ? "" : text.replace("\r", "")).split("\n"); | |
| 412 | + for (String segment : rawLines) { | |
| 413 | + if (segment.isEmpty()) { | |
| 414 | + lines.add(""); | |
| 415 | + continue; | |
| 416 | + } | |
| 417 | + StringBuilder current = new StringBuilder(); | |
| 418 | + for (int i = 0; i < segment.length(); i++) { | |
| 419 | + char c = segment.charAt(i); | |
| 420 | + String candidate = current.toString() + c; | |
| 421 | + if (current.length() > 0 && paint.measureText(candidate) > maxWidth) { | |
| 422 | + lines.add(current.toString()); | |
| 423 | + current.setLength(0); | |
| 424 | + current.append(c); | |
| 425 | + } else { | |
| 426 | + current.append(c); | |
| 427 | + } | |
| 428 | + } | |
| 429 | + if (current.length() > 0) lines.add(current.toString()); | |
| 430 | + } | |
| 431 | + if (lines.isEmpty()) lines.add(""); | |
| 432 | + return lines; | |
| 433 | + } | |
| 434 | + | |
| 435 | + private static void writeBitmapPatch(ByteArrayOutputStream out, BitmapPatch patch) { | |
| 436 | + int bytesPerRow = patch.image.width / 8; | |
| 437 | + addLine(out, "BITMAP " + patch.x + "," + patch.y + "," + bytesPerRow + "," + patch.image.height + ",0,"); | |
| 438 | + for (int y = 0; y < patch.image.height; y++) { | |
| 439 | + for (int byteIndex = 0; byteIndex < bytesPerRow; byteIndex++) { | |
| 440 | + int value = 0; | |
| 441 | + for (int bit = 0; bit < 8; bit++) { | |
| 442 | + int x = byteIndex * 8 + bit; | |
| 443 | + int pixel = patch.image.pixels[y * patch.image.width + x]; | |
| 444 | + if (pixel == 1) value |= (1 << (7 - bit)); | |
| 445 | + } | |
| 446 | + out.write(value & 0xFF); | |
| 447 | + } | |
| 448 | + } | |
| 449 | + out.write('\r'); | |
| 450 | + out.write('\n'); | |
| 451 | + } | |
| 452 | + | |
| 453 | + private static MonochromeImage bitmapToMonochrome(Bitmap bitmap, int threshold) { | |
| 454 | + int bitmapWidth = bitmap.getWidth(); | |
| 455 | + int bitmapHeight = bitmap.getHeight(); | |
| 456 | + int width = ensureMultipleOf8(bitmapWidth); | |
| 457 | + int[] pixels = new int[width * bitmapHeight]; | |
| 458 | + for (int y = 0; y < bitmapHeight; y++) { | |
| 459 | + for (int x = 0; x < width; x++) { | |
| 460 | + if (x >= bitmapWidth) { | |
| 461 | + pixels[y * width + x] = 0; | |
| 462 | + continue; | |
| 463 | + } | |
| 464 | + int color = bitmap.getPixel(x, y); | |
| 465 | + int alpha = (color >>> 24) & 0xFF; | |
| 466 | + int red = (color >>> 16) & 0xFF; | |
| 467 | + int green = (color >>> 8) & 0xFF; | |
| 468 | + int blue = color & 0xFF; | |
| 469 | + double gray = red * 0.299 + green * 0.587 + blue * 0.114; | |
| 470 | + pixels[y * width + x] = alpha <= 10 || gray > threshold ? 0 : 1; | |
| 471 | + } | |
| 472 | + } | |
| 473 | + return new MonochromeImage(width, bitmapHeight, pixels); | |
| 474 | + } | |
| 475 | + | |
| 476 | + private static MonochromeImage invertMonochrome(MonochromeImage image) { | |
| 477 | + if (image == null || image.pixels == null) return image; | |
| 478 | + int[] pixels = new int[image.pixels.length]; | |
| 479 | + for (int i = 0; i < image.pixels.length; i++) { | |
| 480 | + pixels[i] = image.pixels[i] == 1 ? 0 : 1; | |
| 481 | + } | |
| 482 | + return new MonochromeImage(image.width, image.height, pixels); | |
| 483 | + } | |
| 484 | + | |
| 485 | + private static void addLine(ByteArrayOutputStream out, String line) { | |
| 486 | + byte[] bytes = line.getBytes(getPrinterCharset()); | |
| 487 | + out.write(bytes, 0, bytes.length); | |
| 488 | + out.write('\r'); | |
| 489 | + out.write('\n'); | |
| 490 | + } | |
| 491 | + | |
| 492 | + private static Charset getPrinterCharset() { | |
| 493 | + try { | |
| 494 | + return Charset.forName("windows-1252"); | |
| 495 | + } catch (Throwable first) { | |
| 496 | + try { | |
| 497 | + return Charset.forName("Cp1252"); | |
| 498 | + } catch (Throwable second) { | |
| 499 | + return StandardCharsets.ISO_8859_1; | |
| 500 | + } | |
| 501 | + } | |
| 502 | + } | |
| 503 | + | |
| 504 | + private static CharsetEncoder getPrinterEncoder() { | |
| 505 | + try { | |
| 506 | + return getPrinterCharset().newEncoder(); | |
| 507 | + } catch (Throwable first) { | |
| 508 | + try { | |
| 509 | + return StandardCharsets.ISO_8859_1.newEncoder(); | |
| 510 | + } catch (Throwable second) { | |
| 511 | + return null; | |
| 512 | + } | |
| 513 | + } | |
| 514 | + } | |
| 515 | + | |
| 516 | + private static String escapeTscString(String value) { | |
| 517 | + return value == null ? "" : value.replace("\\", "\\\\").replace("\"", "\\\""); | |
| 518 | + } | |
| 519 | + | |
| 520 | + private static String normalizeBarcodeType(String value) { | |
| 521 | + String key = value == null ? "CODE128" : value.trim().toUpperCase(); | |
| 522 | + switch (key) { | |
| 523 | + case "CODE39": return "39"; | |
| 524 | + case "EAN13": return "EAN13"; | |
| 525 | + case "EAN8": return "EAN8"; | |
| 526 | + case "UPCA": return "UPCA"; | |
| 527 | + case "UPCE": return "UPCE"; | |
| 528 | + case "CODABAR": return "CODA"; | |
| 529 | + case "ITF14": return "ITF14"; | |
| 530 | + case "ITF": return "ITF"; | |
| 531 | + default: return "128"; | |
| 532 | + } | |
| 533 | + } | |
| 534 | + | |
| 535 | + private static String normalizeQrLevel(String value) { | |
| 536 | + String key = value == null ? "M" : value.trim().toUpperCase(); | |
| 537 | + if ("L".equals(key) || "M".equals(key) || "Q".equals(key) || "H".equals(key)) return key; | |
| 538 | + return "M"; | |
| 539 | + } | |
| 540 | + | |
| 541 | + private static int resolveQrModuleSize(double widthPx, double heightPx, int dpi, String value, String level) { | |
| 542 | + int targetDots = Math.max(24, Math.min(pxToDots(widthPx, dpi), pxToDots(heightPx, dpi))); | |
| 543 | + int moduleCount = Math.max(21, estimateQrModuleCount(value, level)); | |
| 544 | + return clamp(Math.floorDiv(targetDots, moduleCount), 3, 12); | |
| 545 | + } | |
| 546 | + | |
| 547 | + private static int estimateQrModuleCount(String value, String level) { | |
| 548 | + int length = Math.max(1, value == null ? 0 : value.length()); | |
| 549 | + int[] capacities; | |
| 550 | + switch (level) { | |
| 551 | + case "L": capacities = new int[]{17, 32, 53, 78, 106, 134, 154, 192, 230, 271}; break; | |
| 552 | + case "Q": capacities = new int[]{11, 20, 32, 46, 60, 74, 86, 108, 130, 151}; break; | |
| 553 | + case "H": capacities = new int[]{7, 14, 24, 34, 44, 58, 64, 84, 98, 119}; break; | |
| 554 | + default: capacities = new int[]{14, 26, 42, 62, 84, 106, 122, 152, 180, 213}; | |
| 555 | + } | |
| 556 | + int version = capacities.length; | |
| 557 | + for (int i = 0; i < capacities.length; i++) { | |
| 558 | + if (length <= capacities[i]) { | |
| 559 | + version = i + 1; | |
| 560 | + break; | |
| 561 | + } | |
| 562 | + } | |
| 563 | + return 21 + (version - 1) * 4; | |
| 564 | + } | |
| 565 | + | |
| 566 | + private static int resolveTextScale(double fontSizePx, int dpi) { | |
| 567 | + int targetDots = Math.max(12, (int) Math.round(fontSizePx * dpi / DESIGN_DPI)); | |
| 568 | + return clamp(targetDots / 24.0, 1, 7); | |
| 569 | + } | |
| 570 | + | |
| 571 | + private static int resolveTextX(String align, double xPx, double widthPx, int dpi, String text, int scale) { | |
| 572 | + int left = pxToDots(xPx, dpi); | |
| 573 | + if ("left".equals(align)) return left; | |
| 574 | + int boxWidth = pxToDots(widthPx, dpi); | |
| 575 | + int fontDots = Math.max(24, scale * 24); | |
| 576 | + int textWidth = estimateTextWidthDots(text, fontDots); | |
| 577 | + if ("center".equals(align)) return Math.max(0, left + Math.max(0, boxWidth - textWidth) / 2); | |
| 578 | + return Math.max(0, left + Math.max(0, boxWidth - textWidth)); | |
| 579 | + } | |
| 580 | + | |
| 581 | + private static int estimateTextWidthDots(String text, int fontDots) { | |
| 582 | + double total = 0; | |
| 583 | + for (int i = 0; i < text.length(); i++) { | |
| 584 | + total += text.charAt(i) > 255 ? fontDots : fontDots * 0.6; | |
| 585 | + } | |
| 586 | + return (int) Math.round(total); | |
| 587 | + } | |
| 588 | + | |
| 589 | + private static int clamp(double value, int min, int max) { | |
| 590 | + return Math.max(min, Math.min(max, (int) Math.round(value))); | |
| 591 | + } | |
| 592 | + | |
| 593 | + private static int ensureMultipleOf8(int value) { | |
| 594 | + int safe = Math.max(8, value); | |
| 595 | + return safe % 8 == 0 ? safe : safe + (8 - safe % 8); | |
| 596 | + } | |
| 597 | + | |
| 598 | + private static int pxToDots(double value, int dpi) { | |
| 599 | + return Math.max(0, (int) Math.round(value * dpi / DESIGN_DPI)); | |
| 600 | + } | |
| 601 | + | |
| 602 | + private static double toMillimeter(double value, String unit) { | |
| 603 | + if ("mm".equalsIgnoreCase(unit)) return value; | |
| 604 | + if ("cm".equalsIgnoreCase(unit)) return value * 10; | |
| 605 | + if ("px".equalsIgnoreCase(unit)) return value / DESIGN_DPI * 25.4; | |
| 606 | + return value * 25.4; | |
| 607 | + } | |
| 608 | + | |
| 609 | + private static double round1(double value) { | |
| 610 | + return Math.round(value * 10.0) / 10.0; | |
| 611 | + } | |
| 612 | + | |
| 613 | + private static String formatMm(double value) { | |
| 614 | + return String.format(java.util.Locale.US, "%.1f", value); | |
| 615 | + } | |
| 616 | + | |
| 617 | + private static String getString(JSONObject json, String key, String fallback) { | |
| 618 | + Object value = json.opt(key); | |
| 619 | + return value == null ? fallback : String.valueOf(value); | |
| 620 | + } | |
| 621 | + | |
| 622 | + private static double getDouble(JSONObject json, String key, double fallback) { | |
| 623 | + try { | |
| 624 | + Object value = json.opt(key); | |
| 625 | + if (value == null) return fallback; | |
| 626 | + if (value instanceof Number) return ((Number) value).doubleValue(); | |
| 627 | + return Double.parseDouble(String.valueOf(value)); | |
| 628 | + } catch (Exception e) { | |
| 629 | + return fallback; | |
| 630 | + } | |
| 631 | + } | |
| 632 | + | |
| 633 | + private static boolean getBoolean(JSONObject json, String key, boolean fallback) { | |
| 634 | + try { | |
| 635 | + Object value = json.opt(key); | |
| 636 | + if (value == null) return fallback; | |
| 637 | + if (value instanceof Boolean) return (Boolean) value; | |
| 638 | + return Boolean.parseBoolean(String.valueOf(value)); | |
| 639 | + } catch (Exception e) { | |
| 640 | + return fallback; | |
| 641 | + } | |
| 642 | + } | |
| 643 | + | |
| 644 | + private static final class BitmapPatch { | |
| 645 | + final int x; | |
| 646 | + final int y; | |
| 647 | + final MonochromeImage image; | |
| 648 | + | |
| 649 | + BitmapPatch(int x, int y, MonochromeImage image) { | |
| 650 | + this.x = x; | |
| 651 | + this.y = y; | |
| 652 | + this.image = image; | |
| 653 | + } | |
| 654 | + } | |
| 655 | + | |
| 656 | + private static final class MonochromeImage { | |
| 657 | + final int width; | |
| 658 | + final int height; | |
| 659 | + final int[] pixels; | |
| 660 | + | |
| 661 | + MonochromeImage(int width, int height, int[] pixels) { | |
| 662 | + this.width = width; | |
| 663 | + this.height = height; | |
| 664 | + this.pixels = pixels; | |
| 665 | + } | |
| 666 | + } | |
| 667 | + | |
| 668 | + public static final class BuildResult { | |
| 669 | + public final byte[] bytes; | |
| 670 | + public final int nativeTextCount; | |
| 671 | + public final int rasterTextCount; | |
| 672 | + public final int qrCodeCount; | |
| 673 | + public final int barcodeCount; | |
| 674 | + public final int imagePatchCount; | |
| 675 | + public final int lineCount; | |
| 676 | + public final int elementCount; | |
| 677 | + | |
| 678 | + public BuildResult(byte[] bytes, int nativeTextCount, int rasterTextCount, int qrCodeCount, int barcodeCount, | |
| 679 | + int imagePatchCount, int lineCount, int elementCount) { | |
| 680 | + this.bytes = bytes == null ? new byte[0] : bytes; | |
| 681 | + this.nativeTextCount = nativeTextCount; | |
| 682 | + this.rasterTextCount = rasterTextCount; | |
| 683 | + this.qrCodeCount = qrCodeCount; | |
| 684 | + this.barcodeCount = barcodeCount; | |
| 685 | + this.imagePatchCount = imagePatchCount; | |
| 686 | + this.lineCount = lineCount; | |
| 687 | + this.elementCount = elementCount; | |
| 688 | + } | |
| 689 | + } | |
| 690 | +} | ... | ... |
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/transport/GprinterBluetoothTransport.java
0 → 100644
| 1 | +package com.foodlabel.nativeprinter.transport; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | + | |
| 5 | +import com.gprinter.bean.PrinterDevices; | |
| 6 | +import com.gprinter.io.BluetoothPort; | |
| 7 | +import com.gprinter.io.PortManager; | |
| 8 | +import com.gprinter.utils.CallbackListener; | |
| 9 | +import com.gprinter.utils.Command; | |
| 10 | +import com.gprinter.utils.ConnMethod; | |
| 11 | +import com.foodlabel.nativeprinter.debug.NativePrintDebugState; | |
| 12 | +import com.foodlabel.nativeprinter.support.PluginResult; | |
| 13 | +import com.foodlabel.nativeprinter.support.ThrowableUtils; | |
| 14 | + | |
| 15 | +import java.lang.reflect.Method; | |
| 16 | + | |
| 17 | +public final class GprinterBluetoothTransport { | |
| 18 | + private PortManager portManager; | |
| 19 | + | |
| 20 | + public synchronized PluginResult ensureConnected(String deviceId, String deviceName, NativePrintDebugState debugState) { | |
| 21 | + debugState.setStage("ensureConnected"); | |
| 22 | + debugState.clearError(); | |
| 23 | + | |
| 24 | + if (deviceId == null || deviceId.trim().isEmpty()) { | |
| 25 | + return PluginResult.error(9011003, "Bluetooth device address is empty."); | |
| 26 | + } | |
| 27 | + | |
| 28 | + if (isConnected() && deviceId.equals(debugState.getCurrentDeviceId())) { | |
| 29 | + if (deviceName != null && !deviceName.trim().isEmpty()) { | |
| 30 | + debugState.setCurrentDevice(deviceId, deviceName); | |
| 31 | + } | |
| 32 | + debugState.setStage("connect:ok"); | |
| 33 | + return PluginResult.ok(true, debugState.getCurrentDeviceId(), debugState.getCurrentDeviceName(), "connect:ok"); | |
| 34 | + } | |
| 35 | + | |
| 36 | + disconnect(); | |
| 37 | + Context context = resolveContext(); | |
| 38 | + if (context == null) { | |
| 39 | + return PluginResult.error(9011010, "Unable to resolve Android context for native printer plugin."); | |
| 40 | + } | |
| 41 | + | |
| 42 | + try { | |
| 43 | + PrinterDevices devices = new PrinterDevices.Build() | |
| 44 | + .setContext(context) | |
| 45 | + .setConnMethod(ConnMethod.BLUETOOTH) | |
| 46 | + .setMacAddress(deviceId) | |
| 47 | + .setBlueName(deviceName) | |
| 48 | + .setCommand(Command.TSC) | |
| 49 | + .setCallbackListener(createCallbackListener(debugState)) | |
| 50 | + .build(); | |
| 51 | + BluetoothPort newPort = new BluetoothPort(devices); | |
| 52 | + debugState.setStage("sdk:openPort"); | |
| 53 | + boolean opened = newPort.openPort(); | |
| 54 | + if (!opened || !newPort.getConnectStatus()) { | |
| 55 | + try { | |
| 56 | + newPort.closePort(); | |
| 57 | + } catch (Exception ignored) { | |
| 58 | + } | |
| 59 | + return PluginResult.error(9011004, "Bluetooth sdk openPort failed."); | |
| 60 | + } | |
| 61 | + portManager = newPort; | |
| 62 | + debugState.setCurrentDevice(deviceId, (deviceName != null && !deviceName.trim().isEmpty()) ? deviceName : "Bluetooth Printer"); | |
| 63 | + debugState.setStage("connect:ok"); | |
| 64 | + debugState.clearError(); | |
| 65 | + return PluginResult.ok(true, debugState.getCurrentDeviceId(), debugState.getCurrentDeviceName(), "connect:ok"); | |
| 66 | + } catch (Throwable error) { | |
| 67 | + debugState.setError(ThrowableUtils.unwrap(error)); | |
| 68 | + disconnect(); | |
| 69 | + return PluginResult.error(9011004, debugState.getLastError()); | |
| 70 | + } | |
| 71 | + } | |
| 72 | + | |
| 73 | + public synchronized boolean isConnected() { | |
| 74 | + if (portManager == null) return false; | |
| 75 | + try { | |
| 76 | + return portManager.getConnectStatus(); | |
| 77 | + } catch (Exception e) { | |
| 78 | + return false; | |
| 79 | + } | |
| 80 | + } | |
| 81 | + | |
| 82 | + public synchronized boolean write(byte[] bytes) throws Exception { | |
| 83 | + if (portManager == null) return false; | |
| 84 | + return portManager.writeDataImmediately(bytes); | |
| 85 | + } | |
| 86 | + | |
| 87 | + public synchronized void disconnect() { | |
| 88 | + if (portManager != null) { | |
| 89 | + try { | |
| 90 | + portManager.closePort(); | |
| 91 | + } catch (Exception ignored) { | |
| 92 | + } | |
| 93 | + } | |
| 94 | + portManager = null; | |
| 95 | + } | |
| 96 | + | |
| 97 | + private static CallbackListener createCallbackListener(final NativePrintDebugState debugState) { | |
| 98 | + return new CallbackListener() { | |
| 99 | + @Override | |
| 100 | + public void onConnecting() { | |
| 101 | + debugState.setStage("sdk:onConnecting"); | |
| 102 | + } | |
| 103 | + | |
| 104 | + @Override | |
| 105 | + public void onCheckCommand() { | |
| 106 | + debugState.setStage("sdk:onCheckCommand"); | |
| 107 | + } | |
| 108 | + | |
| 109 | + @Override | |
| 110 | + public void onSuccess(PrinterDevices printerDevices) { | |
| 111 | + debugState.setStage("sdk:onSuccess"); | |
| 112 | + debugState.clearError(); | |
| 113 | + if (printerDevices != null && printerDevices.getBlueName() != null && !printerDevices.getBlueName().trim().isEmpty()) { | |
| 114 | + debugState.setCurrentDevice(debugState.getCurrentDeviceId(), printerDevices.getBlueName()); | |
| 115 | + } | |
| 116 | + } | |
| 117 | + | |
| 118 | + @Override | |
| 119 | + public void onReceive(byte[] bytes) { | |
| 120 | + debugState.setStage("sdk:onReceive"); | |
| 121 | + } | |
| 122 | + | |
| 123 | + @Override | |
| 124 | + public void onFailure() { | |
| 125 | + debugState.setStage("sdk:onFailure"); | |
| 126 | + debugState.setError("Bluetooth sdk reported onFailure."); | |
| 127 | + } | |
| 128 | + | |
| 129 | + @Override | |
| 130 | + public void onDisconnect() { | |
| 131 | + debugState.setStage("sdk:onDisconnect"); | |
| 132 | + } | |
| 133 | + }; | |
| 134 | + } | |
| 135 | + | |
| 136 | + private static Context resolveContext() { | |
| 137 | + try { | |
| 138 | + Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); | |
| 139 | + Method currentApplication = activityThreadClass.getMethod("currentApplication"); | |
| 140 | + Object application = currentApplication.invoke(null); | |
| 141 | + if (application instanceof Context) { | |
| 142 | + return ((Context) application).getApplicationContext(); | |
| 143 | + } | |
| 144 | + } catch (Exception ignored) { | |
| 145 | + } | |
| 146 | + | |
| 147 | + try { | |
| 148 | + Class<?> appGlobalsClass = Class.forName("android.app.AppGlobals"); | |
| 149 | + Method getInitialApplication = appGlobalsClass.getMethod("getInitialApplication"); | |
| 150 | + Object application = getInitialApplication.invoke(null); | |
| 151 | + if (application instanceof Context) { | |
| 152 | + return ((Context) application).getApplicationContext(); | |
| 153 | + } | |
| 154 | + } catch (Exception ignored) { | |
| 155 | + } | |
| 156 | + return null; | |
| 157 | + } | |
| 158 | +} | ... | ... |
打印机安卓基座/native-fast-printer/android/native_fast_printer-release.aar
0 → 100644
No preview for this file type
打印机安卓基座/native-fast-printer/package.json
0 → 100644
| 1 | +{ | |
| 2 | + "name": "native-fast-printer", | |
| 3 | + "id": "native-fast-printer", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "description": "Android高速标签打印原生插件", | |
| 6 | + "_dp_type": "nativeplugin", | |
| 7 | + "_dp_nativeplugin": { | |
| 8 | + "android": { | |
| 9 | + "plugins": [ | |
| 10 | + { | |
| 11 | + "type": "module", | |
| 12 | + "name": "native-fast-printer", | |
| 13 | + "class": "com.foodlabel.nativeprinter.NativeFastPrinterModule" | |
| 14 | + } | |
| 15 | + ], | |
| 16 | + "integrateType": "aar", | |
| 17 | + "dependencies_remark": "使用本地AAR,依赖HBuilder基座内置UniModule/JSCallback/fastjson。", | |
| 18 | + "dependencies": [], | |
| 19 | + "compileOptions": { | |
| 20 | + "sourceCompatibility": "1.8", | |
| 21 | + "targetCompatibility": "1.8" | |
| 22 | + }, | |
| 23 | + "abis": [ | |
| 24 | + "armeabi-v7a", | |
| 25 | + "arm64-v8a", | |
| 26 | + "x86" | |
| 27 | + ], | |
| 28 | + "minSdkVersion": "21", | |
| 29 | + "useAndroidX": true, | |
| 30 | + "permissions": [ | |
| 31 | + "android.permission.BLUETOOTH", | |
| 32 | + "android.permission.BLUETOOTH_ADMIN", | |
| 33 | + "android.permission.BLUETOOTH_CONNECT" | |
| 34 | + ] | |
| 35 | + } | |
| 36 | + } | |
| 37 | +} | ... | ... |
打印机安卓基座/native-fast-printer/sync-to-uniapp.sh
0 → 100755
| 1 | +#!/bin/bash | |
| 2 | +set -euo pipefail | |
| 3 | + | |
| 4 | +PLUGIN_DIR="$(cd "$(dirname "$0")" && pwd)" | |
| 5 | +PROJECT_ROOT="$(cd "$PLUGIN_DIR/../.." && pwd)" | |
| 6 | +UNIAPP_PLUGIN_DIR="$PROJECT_ROOT/美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer" | |
| 7 | + | |
| 8 | +rm -rf "$UNIAPP_PLUGIN_DIR" | |
| 9 | +mkdir -p "$UNIAPP_PLUGIN_DIR/android" | |
| 10 | + | |
| 11 | +cp "$PLUGIN_DIR/package.json" "$UNIAPP_PLUGIN_DIR/package.json" | |
| 12 | +cp "$PLUGIN_DIR/README.md" "$UNIAPP_PLUGIN_DIR/README.md" | |
| 13 | +cp "$PLUGIN_DIR/android/native_fast_printer-release.aar" "$UNIAPP_PLUGIN_DIR/android/" | |
| 14 | + | |
| 15 | +echo "Synced native-fast-printer to: $UNIAPP_PLUGIN_DIR" | ... | ... |
美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/README.md
0 → 100644
| 1 | +# native-fast-printer | |
| 2 | + | |
| 3 | +传统 `nativeplugins` Android 原生插件版高速标签打印模块。 | |
| 4 | + | |
| 5 | +## 能力 | |
| 6 | +- 经典蓝牙连接 / 断开 / 状态 | |
| 7 | +- 接收系统模板 JSON | |
| 8 | +- 原生生成 TSC 指令 | |
| 9 | +- 文本、价格、条码、二维码、横线、图片 | |
| 10 | +- 特殊字符文本和图片走原生位图补丁 | |
| 11 | + | |
| 12 | +## 前端调用 | |
| 13 | +```js | |
| 14 | +const printer = uni.requireNativePlugin('native-fast-printer') | |
| 15 | +``` | |
| 16 | + | |
| 17 | +## 方法 | |
| 18 | +- `connect(params, callback)` | |
| 19 | +- `disconnect(callback)` | |
| 20 | +- `isConnected(callback)` | |
| 21 | +- `printTemplate(params, callback)` | |
| 22 | + | |
| 23 | +## 源码位置 | |
| 24 | +- 当前目录是源码主目录 | |
| 25 | +- `美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/` 是同步后的 uni-app 打包镜像 | |
| 26 | + | |
| 27 | +## 目录结构 | |
| 28 | +- `android-src/src/com/foodlabel/nativeprinter/` | |
| 29 | + - `NativeFastPrinterModule.java`:uni-app 原生模块入口 | |
| 30 | + - `transport/`:蓝牙连接与 SDK 传输层 | |
| 31 | + - `template/`:系统模板 JSON → TSC 指令 | |
| 32 | + - `debug/`:调试状态与统计信息 | |
| 33 | + - `support/`:结果对象、JSON 读取、异常展开 | |
| 34 | +- `android/`:编译产物 AAR | |
| 35 | +- `sync-to-uniapp.sh`:同步到 uni-app 打包镜像 | |
| 36 | + | |
| 37 | +## 说明 | |
| 38 | +1. 修改源码后执行 `android-src/build-aar.sh` | |
| 39 | +2. 再执行 `sync-to-uniapp.sh` | |
| 40 | +3. 重新打包 uni-app 自定义基座 | ... | ... |
美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/android/native_fast_printer-release.aar
0 → 100644
No preview for this file type
美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/package.json
0 → 100644
| 1 | +{ | |
| 2 | + "name": "native-fast-printer", | |
| 3 | + "id": "native-fast-printer", | |
| 4 | + "version": "1.0.0", | |
| 5 | + "description": "Android高速标签打印原生插件", | |
| 6 | + "_dp_type": "nativeplugin", | |
| 7 | + "_dp_nativeplugin": { | |
| 8 | + "android": { | |
| 9 | + "plugins": [ | |
| 10 | + { | |
| 11 | + "type": "module", | |
| 12 | + "name": "native-fast-printer", | |
| 13 | + "class": "com.foodlabel.nativeprinter.NativeFastPrinterModule" | |
| 14 | + } | |
| 15 | + ], | |
| 16 | + "integrateType": "aar", | |
| 17 | + "dependencies_remark": "使用本地AAR,依赖HBuilder基座内置UniModule/JSCallback/fastjson。", | |
| 18 | + "dependencies": [], | |
| 19 | + "compileOptions": { | |
| 20 | + "sourceCompatibility": "1.8", | |
| 21 | + "targetCompatibility": "1.8" | |
| 22 | + }, | |
| 23 | + "abis": [ | |
| 24 | + "armeabi-v7a", | |
| 25 | + "arm64-v8a", | |
| 26 | + "x86" | |
| 27 | + ], | |
| 28 | + "minSdkVersion": "21", | |
| 29 | + "useAndroidX": true, | |
| 30 | + "permissions": [ | |
| 31 | + "android.permission.BLUETOOTH", | |
| 32 | + "android.permission.BLUETOOTH_ADMIN", | |
| 33 | + "android.permission.BLUETOOTH_CONNECT" | |
| 34 | + ] | |
| 35 | + } | |
| 36 | + } | |
| 37 | +} | ... | ... |
美国版/Food Labeling Management App UniApp/src/manifest.json
| ... | ... | @@ -2,8 +2,8 @@ |
| 2 | 2 | "name" : "food.labeling", |
| 3 | 3 | "appid" : "__UNI__1BFD76D", |
| 4 | 4 | "description" : "", |
| 5 | - "versionName" : "1.0.3", | |
| 6 | - "versionCode" : 103, | |
| 5 | + "versionName" : "1.0.5", | |
| 6 | + "versionCode" : 105, | |
| 7 | 7 | "transformPx" : false, |
| 8 | 8 | /* 5+App特有相关 */ |
| 9 | 9 | "app-plus" : { |
| ... | ... | @@ -104,6 +104,20 @@ |
| 104 | 104 | "pid" : "", |
| 105 | 105 | "parameters" : {} |
| 106 | 106 | } |
| 107 | + }, | |
| 108 | + "native-fast-printer" : { | |
| 109 | + "__plugin_info__" : { | |
| 110 | + "name" : "native-fast-printer", | |
| 111 | + "description" : "Android高速标签打印原生插件", | |
| 112 | + "platforms" : "Android", | |
| 113 | + "url" : "", | |
| 114 | + "android_package_name" : "", | |
| 115 | + "ios_bundle_id" : "", | |
| 116 | + "isCloud" : false, | |
| 117 | + "bought" : -1, | |
| 118 | + "pid" : "", | |
| 119 | + "parameters" : {} | |
| 120 | + } | |
| 107 | 121 | } |
| 108 | 122 | } |
| 109 | 123 | }, | ... | ... |
美国版/Food Labeling Management App UniApp/src/pages/labels/bluetooth.vue
| ... | ... | @@ -52,11 +52,16 @@ |
| 52 | 52 | <text class="debug-item">Device Brand: {{ deviceIdentity.brand || '-' }}</text> |
| 53 | 53 | <text class="debug-item">Device Product: {{ deviceIdentity.product || '-' }}</text> |
| 54 | 54 | <text class="debug-item">Classic Module: {{ debugInfo.classicModuleReady ? 'Ready' : 'Not Ready' }}</text> |
| 55 | + <text class="debug-item">Native Plugin: {{ nativeDebug.available ? 'Ready' : 'Missing' }}</text> | |
| 56 | + <text class="debug-item">Native Backend: {{ nativeDebug.backend || '-' }}</text> | |
| 57 | + <text class="debug-item">Native Stage: {{ nativeDebug.stage || '-' }}</text> | |
| 58 | + <text class="debug-item">Native Command Bytes: {{ nativeDebug.commandBytes || 0 }}</text> | |
| 55 | 59 | <text class="debug-item">Paired Count: {{ debugInfo.pairedCount }}</text> |
| 56 | 60 | <text class="debug-item">Virtual BT Printer: {{ debugInfo.foundVirtualPrinter ? 'Found' : 'Not Found' }}</text> |
| 57 | 61 | <text class="debug-item">Classic Scan: {{ debugInfo.lastClassicEvent }}</text> |
| 58 | 62 | <text class="debug-item">BLE Scan: {{ debugInfo.lastBleEvent }}</text> |
| 59 | 63 | <text v-if="debugInfo.lastBleError" class="debug-item debug-error">{{ debugInfo.lastBleError }}</text> |
| 64 | + <text v-if="nativeDebug.lastError" class="debug-item debug-error">{{ nativeDebug.lastError }}</text> | |
| 60 | 65 | <text v-if="debugInfo.locationServiceRequired" class="debug-item debug-warn"> |
| 61 | 66 | Android system Location service is OFF. Turn it on before BLE scan. |
| 62 | 67 | </text> |
| ... | ... | @@ -183,6 +188,10 @@ import { |
| 183 | 188 | } from '../../utils/print/printerConnection' |
| 184 | 189 | import { ensureBluetoothPermissions } from '../../utils/print/bluetoothPermissions' |
| 185 | 190 | import { |
| 191 | + getNativeFastPrinterDebugInfo, | |
| 192 | + getNativeFastPrinterState, | |
| 193 | +} from '../../utils/print/nativeFastPrinter' | |
| 194 | +import { | |
| 186 | 195 | connectBluetoothPrinter, |
| 187 | 196 | describeDiscoveredPrinter, |
| 188 | 197 | disconnectCurrentPrinter, |
| ... | ... | @@ -216,6 +225,7 @@ const debugInfo = ref({ |
| 216 | 225 | lastBleError: '', |
| 217 | 226 | locationServiceRequired: false, |
| 218 | 227 | }) |
| 228 | +const nativeDebug = ref(getNativeFastPrinterState() || {}) | |
| 219 | 229 | |
| 220 | 230 | interface BtDevice { |
| 221 | 231 | deviceId: string |
| ... | ... | @@ -234,6 +244,19 @@ function refreshCurrentPrinter () { |
| 234 | 244 | debugInfo.value.currentMode = currentPrinter.value.type || 'none' |
| 235 | 245 | } |
| 236 | 246 | |
| 247 | +async function refreshNativeDebug () { | |
| 248 | + nativeDebug.value = getNativeFastPrinterState() || {} | |
| 249 | + try { | |
| 250 | + const info = await getNativeFastPrinterDebugInfo() | |
| 251 | + nativeDebug.value = { | |
| 252 | + ...nativeDebug.value, | |
| 253 | + ...info, | |
| 254 | + } | |
| 255 | + } catch (_) { | |
| 256 | + nativeDebug.value = getNativeFastPrinterState() || {} | |
| 257 | + } | |
| 258 | +} | |
| 259 | + | |
| 237 | 260 | function hasPreferredClassicDeviceInList () { |
| 238 | 261 | return devices.value.some((item: any) => { |
| 239 | 262 | const name = String(item?.name || '').toLowerCase() |
| ... | ... | @@ -486,9 +509,11 @@ const handleConnect = async (dev: BtDevice) => { |
| 486 | 509 | try { |
| 487 | 510 | await connectBluetoothPrinter(dev) |
| 488 | 511 | refreshCurrentPrinter() |
| 512 | + await refreshNativeDebug() | |
| 489 | 513 | connectingId.value = '' |
| 490 | 514 | uni.showToast({ title: 'Connected!', icon: 'success' }) |
| 491 | 515 | } catch (e: any) { |
| 516 | + await refreshNativeDebug() | |
| 492 | 517 | errorMsg.value = (e && e.message) ? e.message : 'Connection failed' |
| 493 | 518 | connectingId.value = '' |
| 494 | 519 | } |
| ... | ... | @@ -505,13 +530,13 @@ const handleTestPrint = async () => { |
| 505 | 530 | if (testPrinting.value) return |
| 506 | 531 | testPrinting.value = true |
| 507 | 532 | try { |
| 508 | - uni.showLoading({ title: 'Preparing test print...', mask: true }) | |
| 509 | - await testPrintCurrentPrinter((p) => { | |
| 510 | - if (p < 100) uni.showLoading({ title: `Printing ${p}%`, mask: true }) | |
| 511 | - }) | |
| 533 | + uni.showLoading({ title: 'Sending test job...', mask: true }) | |
| 534 | + await testPrintCurrentPrinter() | |
| 535 | + await refreshNativeDebug() | |
| 512 | 536 | uni.hideLoading() |
| 513 | 537 | uni.showToast({ title: 'Test print sent!', icon: 'success' }) |
| 514 | 538 | } catch (e: any) { |
| 539 | + await refreshNativeDebug() | |
| 515 | 540 | uni.hideLoading() |
| 516 | 541 | const msg = (e && e.message) ? e.message : 'Please check printer connection.' |
| 517 | 542 | if (msg === 'BUILTIN_PLUGIN_NOT_FOUND') { |
| ... | ... | @@ -536,11 +561,13 @@ const handleTestPrint = async () => { |
| 536 | 561 | const handleDisconnect = async () => { |
| 537 | 562 | await disconnectCurrentPrinter() |
| 538 | 563 | refreshCurrentPrinter() |
| 564 | + await refreshNativeDebug() | |
| 539 | 565 | uni.showToast({ title: 'Disconnected', icon: 'none' }) |
| 540 | 566 | } |
| 541 | 567 | |
| 542 | 568 | onMounted(() => { |
| 543 | 569 | debugInfo.value.classicModuleReady = !!classicBluetooth |
| 570 | + refreshNativeDebug() | |
| 544 | 571 | uni.onBluetoothDeviceFound(onDeviceFound) |
| 545 | 572 | uni.onBluetoothAdapterStateChange((res: any) => { |
| 546 | 573 | if (!res.available) { | ... | ... |
美国版/Food Labeling Management App UniApp/src/pages/labels/preview.vue
| ... | ... | @@ -249,13 +249,10 @@ const handlePrint = async () => { |
| 249 | 249 | } |
| 250 | 250 | isPrinting.value = true |
| 251 | 251 | try { |
| 252 | - uni.showLoading({ title: 'Preparing print data...', mask: true }) | |
| 252 | + uni.showLoading({ title: 'Sending print job...', mask: true }) | |
| 253 | 253 | await new Promise(resolve => setTimeout(resolve, 30)) |
| 254 | 254 | await printSystemTemplateForCurrentPrinter(PREVIEW_SYSTEM_TEMPLATE, printTemplateData.value, { |
| 255 | 255 | printQty: printQty.value, |
| 256 | - }, (percent) => { | |
| 257 | - if (percent >= 100) return | |
| 258 | - uni.showLoading({ title: `Printing ${percent}%`, mask: true }) | |
| 259 | 256 | }) |
| 260 | 257 | uni.hideLoading() |
| 261 | 258 | uni.showToast({ | ... | ... |
美国版/Food Labeling Management App UniApp/src/utils/print/manager/printerManager.ts
| ... | ... | @@ -11,7 +11,14 @@ import classicBluetooth from '../bluetoothTool.js' |
| 11 | 11 | import { rasterizeImageData, rasterizeImageForPrinter } from '../imageRaster' |
| 12 | 12 | import { buildEscPosImageData, buildEscPosTemplateData } from '../protocols/escPosBuilder' |
| 13 | 13 | import { buildTscImageData, buildTscTemplateData } from '../protocols/tscProtocol' |
| 14 | +import { | |
| 15 | + connectNativeFastPrinter as connectNativeFastPrinterPlugin, | |
| 16 | + disconnectNativeFastPrinter as disconnectNativeFastPrinterPlugin, | |
| 17 | + isNativeFastPrinterAvailable, | |
| 18 | + printNativeFastTemplate as printNativeFastTemplatePlugin, | |
| 19 | +} from '../nativeFastPrinter' | |
| 14 | 20 | import { adaptSystemLabelTemplate } from '../systemTemplateAdapter' |
| 21 | +import { TEST_PRINT_SYSTEM_TEMPLATE, TEST_PRINT_TEMPLATE_DATA } from '../templates/testPrintTemplate' | |
| 15 | 22 | import { describePrinterCandidate, getPrinterDriverByKey, resolvePrinterDriver } from './driverRegistry' |
| 16 | 23 | import type { |
| 17 | 24 | CurrentPrinterSummary, |
| ... | ... | @@ -34,25 +41,25 @@ function getPrinterTypeDisplayName (type: '' | 'bluetooth' | 'builtin'): string |
| 34 | 41 | function connectClassicBluetooth (device: PrinterCandidate, driver: PrinterDriver): Promise<void> { |
| 35 | 42 | return new Promise((resolve, reject) => { |
| 36 | 43 | // #ifdef APP-PLUS |
| 37 | - const classic = classicBluetooth | |
| 38 | - if (!classic || !classic.connDevice) { | |
| 39 | - reject(new Error('Classic Bluetooth not available. Ensure app is running on the device.')) | |
| 40 | - return | |
| 41 | - } | |
| 42 | - classic.connDevice(device.deviceId, (ok: boolean) => { | |
| 43 | - if (!ok) { | |
| 44 | - reject(new Error('Classic Bluetooth connection failed.')) | |
| 45 | - return | |
| 46 | - } | |
| 47 | - setBluetoothConnection({ | |
| 44 | + if (isNativeFastPrinterAvailable()) { | |
| 45 | + connectNativeFastPrinterPlugin({ | |
| 48 | 46 | deviceId: device.deviceId, |
| 49 | 47 | deviceName: device.name || 'Bluetooth Printer', |
| 50 | - deviceType: 'classic', | |
| 51 | - driverKey: driver.key, | |
| 52 | - mtu: driver.preferredBleMtu || 20, | |
| 48 | + }).then(() => { | |
| 49 | + setBluetoothConnection({ | |
| 50 | + deviceId: device.deviceId, | |
| 51 | + deviceName: device.name || 'Bluetooth Printer', | |
| 52 | + deviceType: 'classic', | |
| 53 | + driverKey: driver.key, | |
| 54 | + mtu: driver.preferredBleMtu || 20, | |
| 55 | + }) | |
| 56 | + resolve() | |
| 57 | + }).catch((error: any) => { | |
| 58 | + reject(error instanceof Error ? error : new Error(String(error || 'Classic Bluetooth connection failed.'))) | |
| 53 | 59 | }) |
| 54 | - resolve() | |
| 55 | - }) | |
| 60 | + return | |
| 61 | + } | |
| 62 | + reject(new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.')) | |
| 56 | 63 | // #endif |
| 57 | 64 | // #ifndef APP-PLUS |
| 58 | 65 | reject(new Error('Classic Bluetooth requires the app.')) |
| ... | ... | @@ -207,8 +214,40 @@ export function getCurrentPrinterSummary (): CurrentPrinterSummary { |
| 207 | 214 | } |
| 208 | 215 | } |
| 209 | 216 | |
| 217 | +function canUseNativeFastTemplatePrint (driver: PrinterDriver): boolean { | |
| 218 | + const connection = getBluetoothConnection() | |
| 219 | + return driver.protocol === 'tsc' | |
| 220 | + && connection?.deviceType === 'classic' | |
| 221 | + && isNativeFastPrinterAvailable() | |
| 222 | +} | |
| 223 | + | |
| 224 | +function getNativeClassicConnection () { | |
| 225 | + const connection = getBluetoothConnection() | |
| 226 | + if (!connection || connection.deviceType !== 'classic') return null | |
| 227 | + return connection | |
| 228 | +} | |
| 229 | + | |
| 210 | 230 | export async function testPrintCurrentPrinter (onProgress?: (percent: number) => void): Promise<PrinterDriver> { |
| 211 | 231 | const driver = getCurrentPrinterDriver() |
| 232 | + const connection = getBluetoothConnection() | |
| 233 | + if (driver.protocol === 'tsc' && connection?.deviceType === 'classic' && !isNativeFastPrinterAvailable()) { | |
| 234 | + throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.') | |
| 235 | + } | |
| 236 | + if (canUseNativeFastTemplatePrint(driver)) { | |
| 237 | + const nativeConnection = getNativeClassicConnection() | |
| 238 | + if (nativeConnection) { | |
| 239 | + await printNativeFastTemplatePlugin({ | |
| 240 | + deviceId: nativeConnection.deviceId, | |
| 241 | + deviceName: nativeConnection.deviceName, | |
| 242 | + template: TEST_PRINT_SYSTEM_TEMPLATE, | |
| 243 | + data: TEST_PRINT_TEMPLATE_DATA, | |
| 244 | + dpi: driver.imageDpi || 203, | |
| 245 | + printQty: 1, | |
| 246 | + }) | |
| 247 | + if (onProgress) onProgress(100) | |
| 248 | + return driver | |
| 249 | + } | |
| 250 | + } | |
| 212 | 251 | await sendToPrinter(driver.buildTestPrintData(), onProgress) |
| 213 | 252 | return driver |
| 214 | 253 | } |
| ... | ... | @@ -279,6 +318,26 @@ export async function printSystemTemplateForCurrentPrinter ( |
| 279 | 318 | onProgress?: (percent: number) => void |
| 280 | 319 | ): Promise<PrinterDriver> { |
| 281 | 320 | const driver = getCurrentPrinterDriver() |
| 321 | + const connection = getBluetoothConnection() | |
| 322 | + if (driver.protocol === 'tsc' && connection?.deviceType === 'classic' && !isNativeFastPrinterAvailable()) { | |
| 323 | + throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.') | |
| 324 | + } | |
| 325 | + if (canUseNativeFastTemplatePrint(driver)) { | |
| 326 | + const nativeConnection = getNativeClassicConnection() | |
| 327 | + if (nativeConnection) { | |
| 328 | + await printNativeFastTemplatePlugin({ | |
| 329 | + deviceId: nativeConnection.deviceId, | |
| 330 | + deviceName: nativeConnection.deviceName, | |
| 331 | + template, | |
| 332 | + data, | |
| 333 | + dpi: driver.imageDpi || 203, | |
| 334 | + printQty: options.printQty || 1, | |
| 335 | + }) | |
| 336 | + if (onProgress) onProgress(100) | |
| 337 | + return driver | |
| 338 | + } | |
| 339 | + } | |
| 340 | + | |
| 282 | 341 | const structuredTemplate = adaptSystemLabelTemplate(template, data, { |
| 283 | 342 | dpi: driver.imageDpi || 203, |
| 284 | 343 | printQty: options.printQty || 1, |
| ... | ... | @@ -301,6 +360,15 @@ export function disconnectCurrentPrinter (): Promise<void> { |
| 301 | 360 | |
| 302 | 361 | if (type === 'bluetooth' && connection?.deviceType === 'classic') { |
| 303 | 362 | // #ifdef APP-PLUS |
| 363 | + if (isNativeFastPrinterAvailable()) { | |
| 364 | + disconnectNativeFastPrinterPlugin().catch((e: any) => { | |
| 365 | + console.error('Disconnect native fast printer failed', e) | |
| 366 | + }).finally(() => { | |
| 367 | + clearPrinter() | |
| 368 | + resolve() | |
| 369 | + }) | |
| 370 | + return | |
| 371 | + } | |
| 304 | 372 | try { |
| 305 | 373 | const classic = classicBluetooth |
| 306 | 374 | if (classic && classic.disConnDevice) classic.disConnDevice() | ... | ... |
美国版/Food Labeling Management App UniApp/src/utils/print/nativeBitmapPatch.ts
0 → 100644
| 1 | +import type { | |
| 2 | + MonochromeImageData, | |
| 3 | + SystemTemplateElementBase, | |
| 4 | + SystemTemplateTextAlign, | |
| 5 | +} from './types/printer' | |
| 6 | + | |
| 7 | +declare const plus: any | |
| 8 | + | |
| 9 | +const DESIGN_DPI = 96 | |
| 10 | +const DEFAULT_THRESHOLD = 180 | |
| 11 | +const TEXT_PADDING_DOTS = 6 | |
| 12 | + | |
| 13 | +type BitmapPatchItem = { | |
| 14 | + type: 'bitmap' | |
| 15 | + x: number | |
| 16 | + y: number | |
| 17 | + image: MonochromeImageData | |
| 18 | +} | |
| 19 | + | |
| 20 | +function clamp (value: number, min: number, max: number): number { | |
| 21 | + return Math.max(min, Math.min(max, Math.round(value))) | |
| 22 | +} | |
| 23 | + | |
| 24 | +function ensureMultipleOf8 (value: number): number { | |
| 25 | + const safe = Math.max(8, Math.round(value || 0)) | |
| 26 | + return safe % 8 === 0 ? safe : safe + (8 - (safe % 8)) | |
| 27 | +} | |
| 28 | + | |
| 29 | +function pxToDots (value: number, dpi: number): number { | |
| 30 | + return Math.max(0, Math.round((Number(value) || 0) * dpi / DESIGN_DPI)) | |
| 31 | +} | |
| 32 | + | |
| 33 | +function normalizeBase64Payload (source: string): string { | |
| 34 | + const value = String(source || '').trim() | |
| 35 | + if (!value) return '' | |
| 36 | + if (value.startsWith('data:image/')) { | |
| 37 | + const index = value.indexOf(',') | |
| 38 | + return index >= 0 ? value.slice(index + 1) : '' | |
| 39 | + } | |
| 40 | + if (/^[A-Za-z0-9+/=\r\n]+$/.test(value) && value.length > 128) { | |
| 41 | + return value.replace(/\s+/g, '') | |
| 42 | + } | |
| 43 | + return '' | |
| 44 | +} | |
| 45 | + | |
| 46 | +function resolveLocalImagePath (source: string): string { | |
| 47 | + let path = String(source || '').trim() | |
| 48 | + if (!path) return '' | |
| 49 | + if (path.startsWith('file://')) { | |
| 50 | + path = path.replace(/^file:\/\//, '') | |
| 51 | + } | |
| 52 | + // #ifdef APP-PLUS | |
| 53 | + try { | |
| 54 | + const converted = plus.io.convertLocalFileSystemURL(path) | |
| 55 | + if (converted) path = converted | |
| 56 | + } catch (_) {} | |
| 57 | + // #endif | |
| 58 | + try { | |
| 59 | + path = decodeURIComponent(path) | |
| 60 | + } catch (_) {} | |
| 61 | + return path | |
| 62 | +} | |
| 63 | + | |
| 64 | +function getAndroidGraphics () { | |
| 65 | + // #ifdef APP-PLUS | |
| 66 | + try { | |
| 67 | + if (typeof plus === 'undefined' || String(plus.os?.name || '').toLowerCase() !== 'android') return null | |
| 68 | + return { | |
| 69 | + Bitmap: plus.android.importClass('android.graphics.Bitmap'), | |
| 70 | + BitmapFactory: plus.android.importClass('android.graphics.BitmapFactory'), | |
| 71 | + BitmapConfig: plus.android.importClass('android.graphics.Bitmap$Config'), | |
| 72 | + Canvas: plus.android.importClass('android.graphics.Canvas'), | |
| 73 | + Paint: plus.android.importClass('android.graphics.Paint'), | |
| 74 | + Color: plus.android.importClass('android.graphics.Color'), | |
| 75 | + Typeface: plus.android.importClass('android.graphics.Typeface'), | |
| 76 | + Base64: plus.android.importClass('android.util.Base64'), | |
| 77 | + } | |
| 78 | + } catch (error) { | |
| 79 | + console.error('getAndroidGraphics failed', error) | |
| 80 | + return null | |
| 81 | + } | |
| 82 | + // #endif | |
| 83 | + // #ifndef APP-PLUS | |
| 84 | + return null | |
| 85 | + // #endif | |
| 86 | +} | |
| 87 | + | |
| 88 | +function bitmapToMonochromeImage ( | |
| 89 | + bitmap: any, | |
| 90 | + threshold = DEFAULT_THRESHOLD | |
| 91 | +): MonochromeImageData { | |
| 92 | + const bitmapWidth = Number(bitmap.getWidth ? bitmap.getWidth() : 0) | |
| 93 | + const bitmapHeight = Number(bitmap.getHeight ? bitmap.getHeight() : 0) | |
| 94 | + const width = ensureMultipleOf8(bitmapWidth) | |
| 95 | + const height = Math.max(1, bitmapHeight) | |
| 96 | + const pixels: number[] = new Array(width * height).fill(0) | |
| 97 | + | |
| 98 | + for (let y = 0; y < height; y++) { | |
| 99 | + for (let x = 0; x < width; x++) { | |
| 100 | + if (x >= bitmapWidth) { | |
| 101 | + pixels[y * width + x] = 0 | |
| 102 | + continue | |
| 103 | + } | |
| 104 | + const color = Number(bitmap.getPixel(x, y)) | |
| 105 | + const alpha = (color >>> 24) & 0xff | |
| 106 | + const red = (color >>> 16) & 0xff | |
| 107 | + const green = (color >>> 8) & 0xff | |
| 108 | + const blue = color & 0xff | |
| 109 | + const gray = red * 0.299 + green * 0.587 + blue * 0.114 | |
| 110 | + pixels[y * width + x] = alpha <= 10 || gray > threshold ? 0 : 1 | |
| 111 | + } | |
| 112 | + } | |
| 113 | + | |
| 114 | + return { width, height, pixels } | |
| 115 | +} | |
| 116 | + | |
| 117 | +function splitTextLines (text: string, paint: any, maxWidth: number): string[] { | |
| 118 | + const lines: string[] = [] | |
| 119 | + const rawLines = String(text || '').replace(/\r/g, '').split('\n') | |
| 120 | + | |
| 121 | + rawLines.forEach((segment) => { | |
| 122 | + if (!segment) { | |
| 123 | + lines.push('') | |
| 124 | + return | |
| 125 | + } | |
| 126 | + | |
| 127 | + let current = '' | |
| 128 | + for (let i = 0; i < segment.length; i++) { | |
| 129 | + const char = segment.charAt(i) | |
| 130 | + const candidate = current + char | |
| 131 | + const measure = Number(paint.measureText(candidate)) | |
| 132 | + if (current && measure > maxWidth) { | |
| 133 | + lines.push(current) | |
| 134 | + current = char | |
| 135 | + } else { | |
| 136 | + current = candidate | |
| 137 | + } | |
| 138 | + } | |
| 139 | + if (current || lines.length === 0) lines.push(current) | |
| 140 | + }) | |
| 141 | + | |
| 142 | + return lines.length > 0 ? lines : [''] | |
| 143 | +} | |
| 144 | + | |
| 145 | +export function shouldRasterizeTextElement (text: string, type: string): boolean { | |
| 146 | + const normalizedType = String(type || '').toUpperCase() | |
| 147 | + if (!text) return false | |
| 148 | + if (normalizedType === 'TEXT_PRICE') return true | |
| 149 | + if (/[€£¥¥$éÉáàâäãåæçèêëìíîïñòóôöõøùúûüýÿœšž]/.test(text)) return true | |
| 150 | + return /[^\x20-\x7E]/.test(text) | |
| 151 | +} | |
| 152 | + | |
| 153 | +export function createTextBitmapPatch (params: { | |
| 154 | + element: SystemTemplateElementBase | |
| 155 | + text: string | |
| 156 | + dpi: number | |
| 157 | + align: SystemTemplateTextAlign | |
| 158 | +}): BitmapPatchItem | null { | |
| 159 | + const graphics = getAndroidGraphics() | |
| 160 | + if (!graphics) return null | |
| 161 | + | |
| 162 | + const { element, text, dpi, align } = params | |
| 163 | + const config = element.config || {} | |
| 164 | + const Bitmap = graphics.Bitmap | |
| 165 | + const BitmapConfig = graphics.BitmapConfig | |
| 166 | + const Canvas = graphics.Canvas | |
| 167 | + const Paint = graphics.Paint | |
| 168 | + const Color = graphics.Color | |
| 169 | + const Typeface = graphics.Typeface | |
| 170 | + | |
| 171 | + const contentWidth = Math.max(8, pxToDots(element.width, dpi)) | |
| 172 | + const width = ensureMultipleOf8(contentWidth + TEXT_PADDING_DOTS * 2) | |
| 173 | + const height = Math.max(16, pxToDots(element.height, dpi) + TEXT_PADDING_DOTS * 2) | |
| 174 | + const bitmap = Bitmap.createBitmap(width, height, BitmapConfig.ARGB_8888) | |
| 175 | + const canvas = new Canvas(bitmap) | |
| 176 | + canvas.drawColor(Color.WHITE) | |
| 177 | + | |
| 178 | + const paint = new Paint() | |
| 179 | + paint.setAntiAlias(true) | |
| 180 | + paint.setDither(true) | |
| 181 | + paint.setColor(Color.BLACK) | |
| 182 | + paint.setSubpixelText(true) | |
| 183 | + const fontSizeDots = Math.max(14, pxToDots(Number(config.fontSize || 14), dpi)) | |
| 184 | + paint.setTextSize(fontSizeDots) | |
| 185 | + const isBold = String(config.fontWeight || '').toLowerCase() === 'bold' || String(element.type || '').toUpperCase() === 'TEXT_PRICE' | |
| 186 | + paint.setFakeBoldText(isBold) | |
| 187 | + paint.setTypeface(isBold ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT) | |
| 188 | + | |
| 189 | + const maxTextWidth = Math.max(8, contentWidth) | |
| 190 | + const lines = splitTextLines(text, paint, maxTextWidth) | |
| 191 | + const fontMetrics = paint.getFontMetrics() | |
| 192 | + const lineHeight = Math.max( | |
| 193 | + fontSizeDots + 2, | |
| 194 | + Math.ceil(Math.abs(Number(fontMetrics.top)) + Math.abs(Number(fontMetrics.bottom)) + 2) | |
| 195 | + ) | |
| 196 | + const totalHeight = lines.length * lineHeight | |
| 197 | + const isCenteredVertically = String(element.type || '').toUpperCase() === 'TEXT_PRICE' | |
| 198 | + const topOffset = isCenteredVertically | |
| 199 | + ? Math.max(TEXT_PADDING_DOTS, Math.floor((height - totalHeight) / 2)) | |
| 200 | + : TEXT_PADDING_DOTS | |
| 201 | + | |
| 202 | + for (let i = 0; i < lines.length; i++) { | |
| 203 | + const line = lines[i] | |
| 204 | + const lineWidth = Number(paint.measureText(line)) | |
| 205 | + let drawX = TEXT_PADDING_DOTS | |
| 206 | + if (align === 'center') { | |
| 207 | + drawX = TEXT_PADDING_DOTS + Math.max(0, Math.round((maxTextWidth - lineWidth) / 2)) | |
| 208 | + } else if (align === 'right') { | |
| 209 | + drawX = TEXT_PADDING_DOTS + Math.max(0, Math.round(maxTextWidth - lineWidth)) | |
| 210 | + } | |
| 211 | + const baseline = topOffset + i * lineHeight - Number(fontMetrics.top) | |
| 212 | + canvas.drawText(line, drawX, baseline, paint) | |
| 213 | + } | |
| 214 | + | |
| 215 | + const image = bitmapToMonochromeImage(bitmap) | |
| 216 | + try { | |
| 217 | + bitmap.recycle && bitmap.recycle() | |
| 218 | + } catch (_) {} | |
| 219 | + | |
| 220 | + return { | |
| 221 | + type: 'bitmap', | |
| 222 | + x: Math.max(0, pxToDots(element.x, dpi) - TEXT_PADDING_DOTS), | |
| 223 | + y: Math.max(0, pxToDots(element.y, dpi) - TEXT_PADDING_DOTS), | |
| 224 | + image, | |
| 225 | + } | |
| 226 | +} | |
| 227 | + | |
| 228 | +function decodeSourceBitmap (source: string, graphics: ReturnType<typeof getAndroidGraphics>) { | |
| 229 | + if (!graphics) return null | |
| 230 | + const rawSource = String(source || '').trim() | |
| 231 | + if (!rawSource) return null | |
| 232 | + | |
| 233 | + const base64Payload = normalizeBase64Payload(rawSource) | |
| 234 | + if (base64Payload) { | |
| 235 | + const bytes = graphics.Base64.decode(base64Payload, 0) | |
| 236 | + return graphics.BitmapFactory.decodeByteArray(bytes, 0, bytes.length) | |
| 237 | + } | |
| 238 | + | |
| 239 | + const localPath = resolveLocalImagePath(rawSource) | |
| 240 | + if (!localPath) return null | |
| 241 | + return graphics.BitmapFactory.decodeFile(localPath) | |
| 242 | +} | |
| 243 | + | |
| 244 | +export function createImageBitmapPatch (params: { | |
| 245 | + element: SystemTemplateElementBase | |
| 246 | + dpi: number | |
| 247 | +}): BitmapPatchItem | null { | |
| 248 | + const graphics = getAndroidGraphics() | |
| 249 | + if (!graphics) return null | |
| 250 | + | |
| 251 | + const { element, dpi } = params | |
| 252 | + const config = element.config || {} | |
| 253 | + const sourceBitmap = decodeSourceBitmap(String(config.src || config.data || config.url || ''), graphics) | |
| 254 | + if (!sourceBitmap) return null | |
| 255 | + | |
| 256 | + const Bitmap = graphics.Bitmap | |
| 257 | + const BitmapConfig = graphics.BitmapConfig | |
| 258 | + const Canvas = graphics.Canvas | |
| 259 | + const Paint = graphics.Paint | |
| 260 | + const Color = graphics.Color | |
| 261 | + | |
| 262 | + const width = ensureMultipleOf8(Math.max(8, pxToDots(element.width, dpi))) | |
| 263 | + const height = Math.max(8, pxToDots(element.height, dpi)) | |
| 264 | + const outputBitmap = Bitmap.createBitmap(width, height, BitmapConfig.ARGB_8888) | |
| 265 | + const canvas = new Canvas(outputBitmap) | |
| 266 | + canvas.drawColor(Color.WHITE) | |
| 267 | + | |
| 268 | + const sourceWidth = Number(sourceBitmap.getWidth ? sourceBitmap.getWidth() : 0) | |
| 269 | + const sourceHeight = Number(sourceBitmap.getHeight ? sourceBitmap.getHeight() : 0) | |
| 270 | + const scaleMode = String(config.scaleMode || 'contain').toLowerCase() | |
| 271 | + | |
| 272 | + let targetWidth = width | |
| 273 | + let targetHeight = height | |
| 274 | + let targetLeft = 0 | |
| 275 | + let targetTop = 0 | |
| 276 | + | |
| 277 | + if (sourceWidth > 0 && sourceHeight > 0 && scaleMode !== 'fill') { | |
| 278 | + const ratio = scaleMode === 'cover' | |
| 279 | + ? Math.max(width / sourceWidth, height / sourceHeight) | |
| 280 | + : Math.min(width / sourceWidth, height / sourceHeight) | |
| 281 | + targetWidth = Math.max(1, Math.round(sourceWidth * ratio)) | |
| 282 | + targetHeight = Math.max(1, Math.round(sourceHeight * ratio)) | |
| 283 | + targetLeft = Math.round((width - targetWidth) / 2) | |
| 284 | + targetTop = Math.round((height - targetHeight) / 2) | |
| 285 | + } | |
| 286 | + | |
| 287 | + const scaledBitmap = Bitmap.createScaledBitmap(sourceBitmap, targetWidth, targetHeight, true) | |
| 288 | + const paint = new Paint() | |
| 289 | + paint.setAntiAlias(true) | |
| 290 | + paint.setFilterBitmap(true) | |
| 291 | + canvas.drawBitmap(scaledBitmap, targetLeft, targetTop, paint) | |
| 292 | + | |
| 293 | + const image = bitmapToMonochromeImage(outputBitmap, Number(config.threshold || DEFAULT_THRESHOLD)) | |
| 294 | + | |
| 295 | + try { | |
| 296 | + scaledBitmap?.recycle && scaledBitmap.recycle() | |
| 297 | + } catch (_) {} | |
| 298 | + try { | |
| 299 | + sourceBitmap?.recycle && sourceBitmap.recycle() | |
| 300 | + } catch (_) {} | |
| 301 | + try { | |
| 302 | + outputBitmap?.recycle && outputBitmap.recycle() | |
| 303 | + } catch (_) {} | |
| 304 | + | |
| 305 | + return { | |
| 306 | + type: 'bitmap', | |
| 307 | + x: pxToDots(element.x, dpi), | |
| 308 | + y: pxToDots(element.y, dpi), | |
| 309 | + image, | |
| 310 | + } | |
| 311 | +} | ... | ... |
美国版/Food Labeling Management App UniApp/src/utils/print/nativeFastPrinter.ts
0 → 100644
| 1 | +import type { LabelTemplateData, SystemLabelTemplate } from './types/printer' | |
| 2 | + | |
| 3 | +type NativePrinterResult = { | |
| 4 | + code?: number | |
| 5 | + msg?: string | |
| 6 | + errMsg?: string | |
| 7 | + connected?: boolean | |
| 8 | + deviceId?: string | |
| 9 | + deviceName?: string | |
| 10 | + success?: boolean | |
| 11 | + backend?: string | |
| 12 | + pluginVersion?: string | |
| 13 | + stage?: string | |
| 14 | + lastError?: string | |
| 15 | + buildMs?: number | |
| 16 | + writeMs?: number | |
| 17 | + commandBytes?: number | |
| 18 | + lastPrintAt?: number | |
| 19 | + nativeTextCount?: number | |
| 20 | + rasterTextCount?: number | |
| 21 | + qrCodeCount?: number | |
| 22 | + barcodeCount?: number | |
| 23 | + imagePatchCount?: number | |
| 24 | + lineCount?: number | |
| 25 | + elementCount?: number | |
| 26 | + available?: boolean | |
| 27 | + lastAction?: string | |
| 28 | +} | |
| 29 | + | |
| 30 | +const nativeFastPrinterState: NativePrinterResult = { | |
| 31 | + available: false, | |
| 32 | + lastAction: 'idle', | |
| 33 | +} | |
| 34 | + | |
| 35 | +function getUniApi (): any { | |
| 36 | + return uni as any | |
| 37 | +} | |
| 38 | + | |
| 39 | +function parsePluginResult (payload: any): NativePrinterResult { | |
| 40 | + if (!payload) return {} | |
| 41 | + if (typeof payload === 'string') { | |
| 42 | + try { | |
| 43 | + return JSON.parse(payload) | |
| 44 | + } catch (_) { | |
| 45 | + return { msg: payload } | |
| 46 | + } | |
| 47 | + } | |
| 48 | + return payload as NativePrinterResult | |
| 49 | +} | |
| 50 | + | |
| 51 | +function updateNativeState (patch: NativePrinterResult) { | |
| 52 | + Object.assign(nativeFastPrinterState, patch, { | |
| 53 | + available: isNativeFastPrinterAvailable(), | |
| 54 | + }) | |
| 55 | +} | |
| 56 | + | |
| 57 | +function getNativePlugin (): any | null { | |
| 58 | + // #ifdef APP-PLUS | |
| 59 | + try { | |
| 60 | + const api = getUniApi() | |
| 61 | + if (typeof api.requireNativePlugin !== 'function') return null | |
| 62 | + const plugin = api.requireNativePlugin('native-fast-printer') | |
| 63 | + return plugin || null | |
| 64 | + } catch (_) { | |
| 65 | + return null | |
| 66 | + } | |
| 67 | + // #endif | |
| 68 | + // #ifndef APP-PLUS | |
| 69 | + return null | |
| 70 | + // #endif | |
| 71 | +} | |
| 72 | + | |
| 73 | +function ensureNativePlugin (): any { | |
| 74 | + const plugin = getNativePlugin() | |
| 75 | + if (!plugin) { | |
| 76 | + updateNativeState({ | |
| 77 | + available: false, | |
| 78 | + lastAction: 'plugin:missing', | |
| 79 | + lastError: 'NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND', | |
| 80 | + }) | |
| 81 | + throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND') | |
| 82 | + } | |
| 83 | + return plugin | |
| 84 | +} | |
| 85 | + | |
| 86 | +export function isNativeFastPrinterAvailable (): boolean { | |
| 87 | + const plugin = getNativePlugin() | |
| 88 | + return !!plugin | |
| 89 | + && typeof plugin.connect === 'function' | |
| 90 | + && typeof plugin.printTemplate === 'function' | |
| 91 | +} | |
| 92 | + | |
| 93 | +export function getNativeFastPrinterState (): NativePrinterResult | null { | |
| 94 | + return { | |
| 95 | + ...nativeFastPrinterState, | |
| 96 | + available: isNativeFastPrinterAvailable(), | |
| 97 | + } | |
| 98 | +} | |
| 99 | + | |
| 100 | +function buildTimeoutError (action: string, timeoutMs: number): Error { | |
| 101 | + const snapshot = getNativeFastPrinterState() | |
| 102 | + const detail = [ | |
| 103 | + `action=${action}`, | |
| 104 | + `timeout=${Math.round(timeoutMs / 1000)}s`, | |
| 105 | + snapshot?.backend ? `backend=${snapshot.backend}` : '', | |
| 106 | + snapshot?.stage ? `stage=${snapshot.stage}` : '', | |
| 107 | + snapshot?.commandBytes ? `commandBytes=${snapshot.commandBytes}` : '', | |
| 108 | + snapshot?.lastError ? `lastError=${snapshot.lastError}` : '', | |
| 109 | + ].filter(Boolean).join('\n') | |
| 110 | + return new Error(`Native printer timeout.\n${detail}`.trim()) | |
| 111 | +} | |
| 112 | + | |
| 113 | +function wrapCallback ( | |
| 114 | + action: string, | |
| 115 | + timeoutMs: number, | |
| 116 | + executor: (resolve: (value: NativePrinterResult) => void, reject: (reason?: any) => void) => void | |
| 117 | +) { | |
| 118 | + return new Promise<NativePrinterResult>((resolve, reject) => { | |
| 119 | + let settled = false | |
| 120 | + const timer = setTimeout(() => { | |
| 121 | + if (settled) return | |
| 122 | + settled = true | |
| 123 | + updateNativeState({ | |
| 124 | + lastAction: `${action}:timeout`, | |
| 125 | + }) | |
| 126 | + reject(buildTimeoutError(action, timeoutMs)) | |
| 127 | + }, timeoutMs) | |
| 128 | + | |
| 129 | + const done = (handler: () => void) => { | |
| 130 | + if (settled) return | |
| 131 | + settled = true | |
| 132 | + clearTimeout(timer) | |
| 133 | + handler() | |
| 134 | + } | |
| 135 | + | |
| 136 | + executor( | |
| 137 | + (value) => done(() => resolve(value)), | |
| 138 | + (reason) => done(() => reject(reason)), | |
| 139 | + ) | |
| 140 | + }) | |
| 141 | +} | |
| 142 | + | |
| 143 | +export function getNativeFastPrinterDebugInfo () { | |
| 144 | + return wrapCallback('getDebugInfo', 5000, (resolve, reject) => { | |
| 145 | + try { | |
| 146 | + const nativePlugin = ensureNativePlugin() | |
| 147 | + if (typeof nativePlugin.getDebugInfo !== 'function') { | |
| 148 | + const snapshot = getNativeFastPrinterState() | |
| 149 | + resolve(snapshot || {}) | |
| 150 | + return | |
| 151 | + } | |
| 152 | + nativePlugin.getDebugInfo((payload: any) => { | |
| 153 | + const res = parsePluginResult(payload) | |
| 154 | + updateNativeState({ | |
| 155 | + ...res, | |
| 156 | + lastAction: 'getDebugInfo', | |
| 157 | + }) | |
| 158 | + resolve(res) | |
| 159 | + }) | |
| 160 | + } catch (error: any) { | |
| 161 | + reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_DEBUG_FAILED'))) | |
| 162 | + } | |
| 163 | + }) | |
| 164 | +} | |
| 165 | + | |
| 166 | +export function connectNativeFastPrinter (options: { | |
| 167 | + deviceId: string | |
| 168 | + deviceName?: string | |
| 169 | +}) { | |
| 170 | + return wrapCallback('connect', 12000, (resolve, reject) => { | |
| 171 | + try { | |
| 172 | + const nativePlugin = ensureNativePlugin() | |
| 173 | + if (typeof nativePlugin.connect !== 'function') { | |
| 174 | + reject(new Error('NATIVE_FAST_PRINTER_CONNECT_METHOD_NOT_FOUND')) | |
| 175 | + return | |
| 176 | + } | |
| 177 | + nativePlugin.connect({ | |
| 178 | + deviceId: options.deviceId, | |
| 179 | + deviceName: options.deviceName || '', | |
| 180 | + }, (payload: any) => { | |
| 181 | + const res = parsePluginResult(payload) | |
| 182 | + updateNativeState({ | |
| 183 | + ...res, | |
| 184 | + lastAction: 'connect', | |
| 185 | + }) | |
| 186 | + if (res.code === 1 || res.success === true) { | |
| 187 | + resolve(res) | |
| 188 | + return | |
| 189 | + } | |
| 190 | + reject(new Error(res.msg || res.errMsg || 'NATIVE_FAST_PRINTER_CONNECT_FAILED')) | |
| 191 | + }) | |
| 192 | + } catch (error: any) { | |
| 193 | + reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_CONNECT_FAILED'))) | |
| 194 | + } | |
| 195 | + }) | |
| 196 | +} | |
| 197 | + | |
| 198 | +export function disconnectNativeFastPrinter () { | |
| 199 | + return wrapCallback('disconnect', 8000, (resolve, reject) => { | |
| 200 | + try { | |
| 201 | + const nativePlugin = ensureNativePlugin() | |
| 202 | + if (typeof nativePlugin.disconnect !== 'function') { | |
| 203 | + reject(new Error('NATIVE_FAST_PRINTER_DISCONNECT_METHOD_NOT_FOUND')) | |
| 204 | + return | |
| 205 | + } | |
| 206 | + nativePlugin.disconnect((payload: any) => { | |
| 207 | + const res = parsePluginResult(payload) | |
| 208 | + updateNativeState({ | |
| 209 | + ...res, | |
| 210 | + lastAction: 'disconnect', | |
| 211 | + }) | |
| 212 | + if (res.code === 1 || res.success === true) { | |
| 213 | + resolve(res) | |
| 214 | + return | |
| 215 | + } | |
| 216 | + reject(new Error(res.msg || res.errMsg || 'NATIVE_FAST_PRINTER_DISCONNECT_FAILED')) | |
| 217 | + }) | |
| 218 | + } catch (error: any) { | |
| 219 | + reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_DISCONNECT_FAILED'))) | |
| 220 | + } | |
| 221 | + }) | |
| 222 | +} | |
| 223 | + | |
| 224 | +export function printNativeFastTemplate (options: { | |
| 225 | + deviceId: string | |
| 226 | + deviceName?: string | |
| 227 | + template: SystemLabelTemplate | |
| 228 | + data?: LabelTemplateData | |
| 229 | + dpi?: number | |
| 230 | + printQty?: number | |
| 231 | +}) { | |
| 232 | + return wrapCallback('printTemplate', 20000, (resolve, reject) => { | |
| 233 | + try { | |
| 234 | + const nativePlugin = ensureNativePlugin() | |
| 235 | + if (typeof nativePlugin.printTemplate !== 'function') { | |
| 236 | + reject(new Error('NATIVE_FAST_PRINTER_PRINT_METHOD_NOT_FOUND')) | |
| 237 | + return | |
| 238 | + } | |
| 239 | + nativePlugin.printTemplate({ | |
| 240 | + deviceId: options.deviceId, | |
| 241 | + deviceName: options.deviceName || '', | |
| 242 | + templateJson: JSON.stringify(options.template), | |
| 243 | + dataJson: JSON.stringify(options.data || {}), | |
| 244 | + dpi: options.dpi || 203, | |
| 245 | + printQty: options.printQty || 1, | |
| 246 | + }, (raw: any) => { | |
| 247 | + const res = parsePluginResult(raw) | |
| 248 | + updateNativeState({ | |
| 249 | + ...res, | |
| 250 | + lastAction: 'printTemplate', | |
| 251 | + }) | |
| 252 | + if (res.code === 1 || res.success === true) { | |
| 253 | + resolve(res) | |
| 254 | + return | |
| 255 | + } | |
| 256 | + reject(new Error(res.msg || res.errMsg || 'NATIVE_FAST_PRINTER_PRINT_FAILED')) | |
| 257 | + }) | |
| 258 | + } catch (error: any) { | |
| 259 | + reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_PRINT_FAILED'))) | |
| 260 | + } | |
| 261 | + }) | |
| 262 | +} | ... | ... |
美国版/Food Labeling Management App UniApp/src/utils/print/protocols/escPosBuilder.ts
| ... | ... | @@ -8,27 +8,61 @@ import type { |
| 8 | 8 | import { resolveEscTemplate } from '../templateRenderer' |
| 9 | 9 | import { createTestPrintTemplate } from '../templates/testPrintTemplate' |
| 10 | 10 | |
| 11 | +function normalizePrinterText (str: string): string { | |
| 12 | + return String(str || '') | |
| 13 | + .normalize('NFKC') | |
| 14 | + .replace(/[\u2018\u2019]/g, '\'') | |
| 15 | + .replace(/[\u201C\u201D]/g, '"') | |
| 16 | + .replace(/[\u2013\u2014]/g, '-') | |
| 17 | +} | |
| 18 | + | |
| 11 | 19 | function stringToBytes (str: string): number[] { |
| 20 | + const normalized = normalizePrinterText(str) | |
| 12 | 21 | const out: number[] = [] |
| 13 | - for (let i = 0; i < str.length; i++) { | |
| 14 | - let c = str.charCodeAt(i) | |
| 15 | - if (c < 0x80) { | |
| 16 | - out.push(c) | |
| 17 | - } else if (c < 0x800) { | |
| 18 | - out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)) | |
| 19 | - } else if (c < 0xd800 || c >= 0xe000) { | |
| 20 | - out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)) | |
| 21 | - } else { | |
| 22 | - i++ | |
| 23 | - const c2 = str.charCodeAt(i) | |
| 24 | - const u = ((c & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000 | |
| 25 | - out.push( | |
| 26 | - 0xf0 | (u >> 18), | |
| 27 | - 0x80 | ((u >> 12) & 0x3f), | |
| 28 | - 0x80 | ((u >> 6) & 0x3f), | |
| 29 | - 0x80 | (u & 0x3f) | |
| 30 | - ) | |
| 22 | + const cp1252Map: Record<number, number> = { | |
| 23 | + 0x20ac: 0x80, | |
| 24 | + 0x201a: 0x82, | |
| 25 | + 0x0192: 0x83, | |
| 26 | + 0x201e: 0x84, | |
| 27 | + 0x2026: 0x85, | |
| 28 | + 0x2020: 0x86, | |
| 29 | + 0x2021: 0x87, | |
| 30 | + 0x02c6: 0x88, | |
| 31 | + 0x2030: 0x89, | |
| 32 | + 0x0160: 0x8a, | |
| 33 | + 0x2039: 0x8b, | |
| 34 | + 0x0152: 0x8c, | |
| 35 | + 0x017d: 0x8e, | |
| 36 | + 0x2018: 0x91, | |
| 37 | + 0x2019: 0x92, | |
| 38 | + 0x201c: 0x93, | |
| 39 | + 0x201d: 0x94, | |
| 40 | + 0x2022: 0x95, | |
| 41 | + 0x2013: 0x96, | |
| 42 | + 0x2014: 0x97, | |
| 43 | + 0x02dc: 0x98, | |
| 44 | + 0x2122: 0x99, | |
| 45 | + 0x0161: 0x9a, | |
| 46 | + 0x203a: 0x9b, | |
| 47 | + 0x0153: 0x9c, | |
| 48 | + 0x017e: 0x9e, | |
| 49 | + 0x0178: 0x9f, | |
| 50 | + } | |
| 51 | + for (let i = 0; i < normalized.length; i++) { | |
| 52 | + const code = normalized.charCodeAt(i) | |
| 53 | + if (code < 0x80) { | |
| 54 | + out.push(code) | |
| 55 | + continue | |
| 56 | + } | |
| 57 | + if (code >= 0xa0 && code <= 0xff) { | |
| 58 | + out.push(code) | |
| 59 | + continue | |
| 60 | + } | |
| 61 | + if (cp1252Map[code] != null) { | |
| 62 | + out.push(cp1252Map[code]) | |
| 63 | + continue | |
| 31 | 64 | } |
| 65 | + out.push(0x3f) | |
| 32 | 66 | } |
| 33 | 67 | return out |
| 34 | 68 | } |
| ... | ... | @@ -104,6 +138,7 @@ function appendBoxLine (out: number[], text = '', width = 32) { |
| 104 | 138 | function createEscDocument (builder: (out: number[]) => void): number[] { |
| 105 | 139 | const out: number[] = [] |
| 106 | 140 | out.push(0x1b, 0x40) |
| 141 | + out.push(0x1b, 0x74, 16) | |
| 107 | 142 | builder(out) |
| 108 | 143 | out.push(0x1b, 0x64, 0x04) |
| 109 | 144 | return out | ... | ... |
美国版/Food Labeling Management App UniApp/src/utils/print/systemTemplateAdapter.ts
| 1 | +import { | |
| 2 | + createImageBitmapPatch, | |
| 3 | + createTextBitmapPatch, | |
| 4 | + shouldRasterizeTextElement, | |
| 5 | +} from './nativeBitmapPatch' | |
| 1 | 6 | import { applyTemplateData } from './templateRenderer' |
| 2 | 7 | import type { |
| 3 | 8 | EscTemplateItem, |
| ... | ... | @@ -119,13 +124,34 @@ function resolveTemplateFieldValue (data: LabelTemplateData, key: string): strin |
| 119 | 124 | return '' |
| 120 | 125 | } |
| 121 | 126 | |
| 127 | +function formatPriceValue ( | |
| 128 | + rawValue: string, | |
| 129 | + config: Record<string, any> | |
| 130 | +): string { | |
| 131 | + const prefix = getConfigString(config, ['prefix'], '') | |
| 132 | + const suffix = getConfigString(config, ['suffix'], '') | |
| 133 | + const decimal = getConfigNumber(config, ['decimal'], -1) | |
| 134 | + const numericValue = Number(rawValue) | |
| 135 | + const value = !Number.isNaN(numericValue) && Number.isFinite(numericValue) && decimal >= 0 | |
| 136 | + ? numericValue.toFixed(decimal) | |
| 137 | + : rawValue | |
| 138 | + return `${prefix}${value}${suffix}` | |
| 139 | +} | |
| 140 | + | |
| 122 | 141 | function resolveElementText ( |
| 123 | 142 | element: SystemTemplateElementBase, |
| 124 | 143 | data: LabelTemplateData |
| 125 | 144 | ): string { |
| 126 | 145 | const config = element.config || {} |
| 146 | + const type = String(element.type || '').toUpperCase() | |
| 127 | 147 | const hasText = config.text != null && config.text !== '' |
| 128 | - if (hasText && String(element.type || '').toUpperCase() === 'TEXT_STATIC') { | |
| 148 | + if (type === 'TEXT_PRICE') { | |
| 149 | + const bindingKey = resolveBindingKey(element) | |
| 150 | + const boundValue = resolveTemplateFieldValue(data, bindingKey) | |
| 151 | + const baseValue = boundValue || (hasText ? applyTemplateData(String(config.text), data) : '') | |
| 152 | + return baseValue ? formatPriceValue(baseValue, config) : '' | |
| 153 | + } | |
| 154 | + if (hasText && type === 'TEXT_STATIC') { | |
| 129 | 155 | return applyTemplateData(String(config.text), data) |
| 130 | 156 | } |
| 131 | 157 | if (hasText && String(config.text).includes('{{')) { |
| ... | ... | @@ -191,6 +217,40 @@ function resolveTextScale (fontSizePx: number, dpi: number): number { |
| 191 | 217 | return clamp(targetDots / 24, 1, 7) |
| 192 | 218 | } |
| 193 | 219 | |
| 220 | +function estimateQrModuleCount (value: string, level: 'L' | 'M' | 'Q' | 'H'): number { | |
| 221 | + const capacities: Record<'L' | 'M' | 'Q' | 'H', number[]> = { | |
| 222 | + L: [17, 32, 53, 78, 106, 134, 154, 192, 230, 271], | |
| 223 | + M: [14, 26, 42, 62, 84, 106, 122, 152, 180, 213], | |
| 224 | + Q: [11, 20, 32, 46, 60, 74, 86, 108, 130, 151], | |
| 225 | + H: [7, 14, 24, 34, 44, 58, 64, 84, 98, 119], | |
| 226 | + } | |
| 227 | + const length = Math.max(1, String(value || '').length) | |
| 228 | + const versions = capacities[level] || capacities.M | |
| 229 | + let version = versions.length | |
| 230 | + for (let i = 0; i < versions.length; i++) { | |
| 231 | + if (length <= versions[i]) { | |
| 232 | + version = i + 1 | |
| 233 | + break | |
| 234 | + } | |
| 235 | + } | |
| 236 | + return 21 + (version - 1) * 4 | |
| 237 | +} | |
| 238 | + | |
| 239 | +function resolveQrModuleSize ( | |
| 240 | + widthPx: number, | |
| 241 | + heightPx: number, | |
| 242 | + dpi: number, | |
| 243 | + value: string, | |
| 244 | + level: 'L' | 'M' | 'Q' | 'H' | |
| 245 | +): number { | |
| 246 | + const targetDots = Math.max(24, Math.min( | |
| 247 | + pxToDots(widthPx, dpi), | |
| 248 | + pxToDots(heightPx, dpi) | |
| 249 | + )) | |
| 250 | + const moduleCount = Math.max(21, estimateQrModuleCount(value, level)) | |
| 251 | + return clamp(Math.floor(targetDots / moduleCount), 3, 12) | |
| 252 | +} | |
| 253 | + | |
| 194 | 254 | function resolveTextX (params: { |
| 195 | 255 | align: SystemTemplateTextAlign |
| 196 | 256 | xPx: number |
| ... | ... | @@ -221,6 +281,8 @@ function buildTscTemplate ( |
| 221 | 281 | const heightMm = roundNumber(toMillimeter(template.height, template.unit || 'inch')) |
| 222 | 282 | const items: TscTemplateItem[] = [] |
| 223 | 283 | |
| 284 | + const pageWidth = templateWidthPx(template) | |
| 285 | + | |
| 224 | 286 | sortElements(template.elements).forEach((element) => { |
| 225 | 287 | const config = element.config || {} |
| 226 | 288 | const type = String(element.type || '').toUpperCase() |
| ... | ... | @@ -229,7 +291,21 @@ function buildTscTemplate ( |
| 229 | 291 | const text = resolveElementText(element, data) |
| 230 | 292 | if (!text) return |
| 231 | 293 | const scale = resolveTextScale(getConfigNumber(config, ['fontSize'], 14), dpi) |
| 232 | - const align = resolveElementAlign(element, templateWidthPx(template)) | |
| 294 | + const align = resolveElementAlign(element, pageWidth) | |
| 295 | + | |
| 296 | + if (shouldRasterizeTextElement(text, type)) { | |
| 297 | + const bitmapPatch = createTextBitmapPatch({ | |
| 298 | + element, | |
| 299 | + text, | |
| 300 | + dpi, | |
| 301 | + align, | |
| 302 | + }) | |
| 303 | + if (bitmapPatch) { | |
| 304 | + items.push(bitmapPatch) | |
| 305 | + return | |
| 306 | + } | |
| 307 | + } | |
| 308 | + | |
| 233 | 309 | items.push({ |
| 234 | 310 | type: 'text', |
| 235 | 311 | x: resolveTextX({ |
| ... | ... | @@ -253,13 +329,14 @@ function buildTscTemplate ( |
| 253 | 329 | if (type === 'QRCODE') { |
| 254 | 330 | const value = resolveElementDataValue(element, data) |
| 255 | 331 | if (!value) return |
| 332 | + const level = normalizeQrLevel(getConfigString(config, ['errorLevel'], 'M')) | |
| 256 | 333 | items.push({ |
| 257 | 334 | type: 'qrcode', |
| 258 | 335 | x: pxToDots(element.x, dpi), |
| 259 | 336 | y: pxToDots(element.y, dpi), |
| 260 | 337 | value, |
| 261 | - level: normalizeQrLevel(getConfigString(config, ['errorLevel'], 'M')), | |
| 262 | - cellWidth: clamp(Math.min(element.width, element.height) / 20, 2, 10), | |
| 338 | + level, | |
| 339 | + cellWidth: resolveQrModuleSize(element.width, element.height, dpi, value, level), | |
| 263 | 340 | mode: 'A', |
| 264 | 341 | }) |
| 265 | 342 | return |
| ... | ... | @@ -280,6 +357,26 @@ function buildTscTemplate ( |
| 280 | 357 | narrow: clamp(element.width / Math.max(40, value.length * 6), 1, 4), |
| 281 | 358 | wide: clamp(element.width / Math.max(24, value.length * 3), 2, 6), |
| 282 | 359 | }) |
| 360 | + return | |
| 361 | + } | |
| 362 | + | |
| 363 | + if (type === 'IMAGE') { | |
| 364 | + const bitmapPatch = createImageBitmapPatch({ | |
| 365 | + element, | |
| 366 | + dpi, | |
| 367 | + }) | |
| 368 | + if (bitmapPatch) items.push(bitmapPatch) | |
| 369 | + return | |
| 370 | + } | |
| 371 | + | |
| 372 | + if (type === 'BLANK' && String(element.border || '').toLowerCase() === 'line') { | |
| 373 | + items.push({ | |
| 374 | + type: 'bar', | |
| 375 | + x: pxToDots(element.x, dpi), | |
| 376 | + y: pxToDots(element.y, dpi), | |
| 377 | + width: Math.max(1, pxToDots(element.width, dpi)), | |
| 378 | + height: Math.max(1, pxToDots(element.height || 1, dpi)), | |
| 379 | + }) | |
| 283 | 380 | } |
| 284 | 381 | }) |
| 285 | 382 | |
| ... | ... | @@ -326,12 +423,13 @@ function buildEscTemplate ( |
| 326 | 423 | if (type === 'QRCODE') { |
| 327 | 424 | const value = resolveElementDataValue(element, data) |
| 328 | 425 | if (!value) return |
| 426 | + const level = normalizeQrLevel(getConfigString(config, ['errorLevel'], 'M')) | |
| 329 | 427 | items.push({ |
| 330 | 428 | type: 'qrcode', |
| 331 | 429 | value, |
| 332 | 430 | align, |
| 333 | - size: clamp(Math.min(element.width, element.height) / 24, 3, 10), | |
| 334 | - level: normalizeQrLevel(getConfigString(config, ['errorLevel'], 'M')), | |
| 431 | + size: resolveQrModuleSize(element.width, element.height, 203, value, level), | |
| 432 | + level, | |
| 335 | 433 | }) |
| 336 | 434 | return |
| 337 | 435 | } |
| ... | ... | @@ -348,6 +446,14 @@ function buildEscTemplate ( |
| 348 | 446 | width: clamp(element.width / Math.max(48, value.length * 4), 2, 6), |
| 349 | 447 | showText: config.showText !== false, |
| 350 | 448 | }) |
| 449 | + return | |
| 450 | + } | |
| 451 | + | |
| 452 | + if (type === 'BLANK' && String(element.border || '').toLowerCase() === 'line') { | |
| 453 | + items.push({ | |
| 454 | + type: 'rule', | |
| 455 | + width: clamp(element.width / 8, 8, 48), | |
| 456 | + }) | |
| 351 | 457 | } |
| 352 | 458 | }) |
| 353 | 459 | ... | ... |
美国版/Food Labeling Management App UniApp/src/utils/print/templates/test.json
| 1 | 1 | { |
| 2 | - "id": "template-1773998862063", | |
| 2 | + "id": "template-1772158111858", | |
| 3 | 3 | "name": "未命名模板", |
| 4 | 4 | "labelType": "PRICE", |
| 5 | 5 | "unit": "inch", |
| 6 | - "width": 4, | |
| 7 | - "height": 2, | |
| 6 | + "width": 3, | |
| 7 | + "height": 6, | |
| 8 | 8 | "appliedLocation": "ALL", |
| 9 | 9 | "showRuler": true, |
| 10 | - "showGrid": true, | |
| 10 | + "showGrid": false, | |
| 11 | 11 | "elements": [ |
| 12 | 12 | { |
| 13 | - "id": "el-1773998886036-34sylni", | |
| 13 | + "id": "el-1774011780062-pxyqycw", | |
| 14 | 14 | "type": "TEXT_STATIC", |
| 15 | - "x": 104, | |
| 16 | - "y": 16, | |
| 15 | + "x": 80, | |
| 16 | + "y": 32, | |
| 17 | 17 | "width": 120, |
| 18 | 18 | "height": 24, |
| 19 | 19 | "rotation": "horizontal", |
| 20 | 20 | "border": "none", |
| 21 | 21 | "config": { |
| 22 | - "text": "文本", | |
| 22 | + "text": "Tasty Café", | |
| 23 | 23 | "fontFamily": "Arial", |
| 24 | - "fontSize": 14, | |
| 24 | + "fontSize": 20, | |
| 25 | 25 | "fontWeight": "normal", |
| 26 | 26 | "textAlign": "center" |
| 27 | 27 | } |
| 28 | 28 | }, |
| 29 | 29 | { |
| 30 | - "id": "el-1773998909568-4jjwdx7", | |
| 30 | + "id": "el-1774011806412-ctz9hgl", | |
| 31 | + "type": "QRCODE", | |
| 32 | + "x": 64, | |
| 33 | + "y": 80, | |
| 34 | + "width": 160, | |
| 35 | + "height": 128, | |
| 36 | + "rotation": "horizontal", | |
| 37 | + "border": "none", | |
| 38 | + "config": { | |
| 39 | + "data": "https://example.com", | |
| 40 | + "errorLevel": "M" | |
| 41 | + } | |
| 42 | + }, | |
| 43 | + { | |
| 44 | + "id": "el-1775020000000-imgtest", | |
| 45 | + "type": "IMAGE", | |
| 46 | + "x": 216, | |
| 47 | + "y": 96, | |
| 48 | + "width": 56, | |
| 49 | + "height": 56, | |
| 50 | + "rotation": "horizontal", | |
| 51 | + "border": "none", | |
| 52 | + "config": { | |
| 53 | + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAAEi6oPRAAAKQ2lDQ1BJQ0MgcHJvZmlsZQAAeNqdU3dYk/cWPt/3ZQ9WQtjwsZdsgQAiI6wIyBBZohCSAGGEEBJAxYWIClYUFRGcSFXEgtUKSJ2I4qAouGdBiohai1VcOO4f3Ke1fXrv7e371/u855zn/M55zw+AERImkeaiagA5UoU8Otgfj09IxMm9gAIVSOAEIBDmy8JnBcUAAPADeXh+dLA//AGvbwACAHDVLiQSx+H/g7pQJlcAIJEA4CIS5wsBkFIAyC5UyBQAyBgAsFOzZAoAlAAAbHl8QiIAqg0A7PRJPgUA2KmT3BcA2KIcqQgAjQEAmShHJAJAuwBgVYFSLALAwgCgrEAiLgTArgGAWbYyRwKAvQUAdo5YkA9AYACAmUIszAAgOAIAQx4TzQMgTAOgMNK/4KlfcIW4SAEAwMuVzZdL0jMUuJXQGnfy8ODiIeLCbLFCYRcpEGYJ5CKcl5sjE0jnA0zODAAAGvnRwf44P5Dn5uTh5mbnbO/0xaL+a/BvIj4h8d/+vIwCBAAQTs/v2l/l5dYDcMcBsHW/a6lbANpWAGjf+V0z2wmgWgrQevmLeTj8QB6eoVDIPB0cCgsL7SViob0w44s+/zPhb+CLfvb8QB7+23rwAHGaQJmtwKOD/XFhbnauUo7nywRCMW735yP+x4V//Y4p0eI0sVwsFYrxWIm4UCJNx3m5UpFEIcmV4hLpfzLxH5b9CZN3DQCshk/ATrYHtctswH7uAQKLDljSdgBAfvMtjBoLkQAQZzQyefcAAJO/+Y9AKwEAzZek4wAAvOgYXKiUF0zGCAAARKCBKrBBBwzBFKzADpzBHbzAFwJhBkRADCTAPBBCBuSAHAqhGJZBGVTAOtgEtbADGqARmuEQtMExOA3n4BJcgetwFwZgGJ7CGLyGCQRByAgTYSE6iBFijtgizggXmY4EImFINJKApCDpiBRRIsXIcqQCqUJqkV1II/ItchQ5jVxA+pDbyCAyivyKvEcxlIGyUQPUAnVAuagfGorGoHPRdDQPXYCWomvRGrQePYC2oqfRS+h1dAB9io5jgNExDmaM2WFcjIdFYIlYGibHFmPlWDVWjzVjHVg3dhUbwJ5h7wgkAouAE+wIXoQQwmyCkJBHWExYQ6gl7CO0EroIVwmDhDHCJyKTqE+0JXoS+cR4YjqxkFhGrCbuIR4hniVeJw4TX5NIJA7JkuROCiElkDJJC0lrSNtILaRTpD7SEGmcTCbrkG3J3uQIsoCsIJeRt5APkE+S+8nD5LcUOsWI4kwJoiRSpJQSSjVlP+UEpZ8yQpmgqlHNqZ7UCKqIOp9aSW2gdlAvU4epEzR1miXNmxZDy6Qto9XQmmlnafdoL+l0ugndgx5Fl9CX0mvoB+nn6YP0dwwNhg2Dx0hiKBlrGXsZpxi3GS+ZTKYF05eZyFQw1zIbmWeYD5hvVVgq9ip8FZHKEpU6lVaVfpXnqlRVc1U/1XmqC1SrVQ+rXlZ9pkZVs1DjqQnUFqvVqR1Vu6k2rs5Sd1KPUM9RX6O+X/2C+mMNsoaFRqCGSKNUY7fGGY0hFsYyZfFYQtZyVgPrLGuYTWJbsvnsTHYF+xt2L3tMU0NzqmasZpFmneZxzQEOxrHg8DnZnErOIc4NznstAy0/LbHWaq1mrX6tN9p62r7aYu1y7Rbt69rvdXCdQJ0snfU6bTr3dQm6NrpRuoW623XP6j7TY+t56Qn1yvUO6d3RR/Vt9KP1F+rv1u/RHzcwNAg2kBlsMThj8MyQY+hrmGm40fCE4agRy2i6kcRoo9FJoye4Ju6HZ+M1eBc+ZqxvHGKsNN5l3Gs8YWJpMtukxKTF5L4pzZRrmma60bTTdMzMyCzcrNisyeyOOdWca55hvtm82/yNhaVFnMVKizaLx5balnzLBZZNlvesmFY+VnlW9VbXrEnWXOss623WV2xQG1ebDJs6m8u2qK2brcR2m23fFOIUjynSKfVTbtox7PzsCuya7AbtOfZh9iX2bfbPHcwcEh3WO3Q7fHJ0dcx2bHC866ThNMOpxKnD6VdnG2ehc53zNRemS5DLEpd2lxdTbaeKp26fesuV5RruutK10/Wjm7ub3K3ZbdTdzD3Ffav7TS6bG8ldwz3vQfTw91jicczjnaebp8LzkOcvXnZeWV77vR5Ps5wmntYwbcjbxFvgvct7YDo+PWX6zukDPsY+Ap96n4e+pr4i3z2+I37Wfpl+B/ye+zv6y/2P+L/hefIW8U4FYAHBAeUBvYEagbMDawMfBJkEpQc1BY0FuwYvDD4VQgwJDVkfcpNvwBfyG/ljM9xnLJrRFcoInRVaG/owzCZMHtYRjobPCN8Qfm+m+UzpzLYIiOBHbIi4H2kZmRf5fRQpKjKqLupRtFN0cXT3LNas5Fn7Z72O8Y+pjLk722q2cnZnrGpsUmxj7Ju4gLiquIF4h/hF8ZcSdBMkCe2J5MTYxD2J43MC52yaM5zkmlSWdGOu5dyiuRfm6c7Lnnc8WTVZkHw4hZgSl7I/5YMgQlAvGE/lp25NHRPyhJuFT0W+oo2iUbG3uEo8kuadVpX2ON07fUP6aIZPRnXGMwlPUit5kRmSuSPzTVZE1t6sz9lx2S05lJyUnKNSDWmWtCvXMLcot09mKyuTDeR55m3KG5OHyvfkI/lz89sVbIVM0aO0Uq5QDhZML6greFsYW3i4SL1IWtQz32b+6vkjC4IWfL2QsFC4sLPYuHhZ8eAiv0W7FiOLUxd3LjFdUrpkeGnw0n3LaMuylv1Q4lhSVfJqedzyjlKD0qWlQyuCVzSVqZTJy26u9Fq5YxVhlWRV72qX1VtWfyoXlV+scKyorviwRrjm4ldOX9V89Xlt2treSrfK7etI66Trbqz3Wb+vSr1qQdXQhvANrRvxjeUbX21K3nShemr1js20zcrNAzVhNe1bzLas2/KhNqP2ep1/XctW/a2rt77ZJtrWv913e/MOgx0VO97vlOy8tSt4V2u9RX31btLugt2PGmIbur/mft24R3dPxZ6Pe6V7B/ZF7+tqdG9s3K+/v7IJbVI2jR5IOnDlm4Bv2pvtmne1cFoqDsJB5cEn36Z8e+NQ6KHOw9zDzd+Zf7f1COtIeSvSOr91rC2jbaA9ob3v6IyjnR1eHUe+t/9+7zHjY3XHNY9XnqCdKD3x+eSCk+OnZKeenU4/PdSZ3Hn3TPyZa11RXb1nQ8+ePxd07ky3X/fJ897nj13wvHD0Ivdi2yW3S609rj1HfnD94UivW2/rZffL7Vc8rnT0Tes70e/Tf/pqwNVz1/jXLl2feb3vxuwbt24m3Ry4Jbr1+Hb27Rd3Cu5M3F16j3iv/L7a/eoH+g/qf7T+sWXAbeD4YMBgz8NZD+8OCYee/pT/04fh0kfMR9UjRiONj50fHxsNGr3yZM6T4aeypxPPyn5W/3nrc6vn3/3i+0vPWPzY8Av5i8+/rnmp83Lvq6mvOscjxx+8znk98ab8rc7bfe+477rfx70fmSj8QP5Q89H6Y8en0E/3Pud8/vwv94Tz+4A5JREAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAADKmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzIgNzkuMTU5Mjg0LCAyMDE2LzA0LzE5LTEzOjEzOjQwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGRkE0MjcxNTdEQzYxMUU4QkZBOERDOEVCQ0U0NTBGMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGRkE0MjcxNDdEQzYxMUU4QkZBOERDOEVCQ0U0NTBGMSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QkE4RkFCN0M3REM1MTFFOEJGQThEQzhFQkNFNDUwRjEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QkE4RkFCN0Q3REM1MTFFOEJGQThEQzhFQkNFNDUwRjEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5BZZ+3AAAB1ElEQVR42mJkAALtmZb/GfAAJkIKwIoYiAA4FV1JO0Ylk0hWxILLHTgV6cyywqoIIIAYiQinb8S4iYs036E7esgEJq6ABAGAACImMBmo5m6yDcLlR5gcNnnaumhADWIhJoOTbRC+9ILPa9+o4TWAAAIlyDVAOphCc1SYqGAICNwZxumIidi8NILz2qhBdCyPaOcicgq1wRnYAAFErRKSgZo+GzSOoWpQD1sHsRCjCDnzkpp90DM+If2jUTbqoFEHjZZDpJYroyFESeNmNFHTykEqg8g9bwACCNRiVAYyLgEx1wA7Zu3V9OMhVBt1opajBlsaCh7NZaMOGnXQgFeupHZjKO1CjUbZqINGHTTqoFEHjTpo1EGjDhqMgw342kejUTaahggpoOdg1WiUjTpoODoIvL7tzSBykB5AgPbtGIdBGIYCaBR16swROEQvzT06cxjm1lRFDC0LcpXC+xJzpIdJhOW8e4z359MVWSde1C32xRYasC0mCmascDZzrQz+7NgABAgQINnRY/iUrb5D9v9l9toqCBAgQIAAAQIESAABAgQIEKCD5ZK9QPaMigoCdIJP7NdjOyoIECBAgGQBGjB8zVDjam153T0OqInJbBAWfdg8AExKZVcA71uIAAAAAElFTkSuQmCC", | |
| 54 | + "scaleMode": "contain" | |
| 55 | + } | |
| 56 | + }, | |
| 57 | + { | |
| 58 | + "id": "el-1774011847653-m8svjm1", | |
| 59 | + "type": "BARCODE", | |
| 60 | + "x": 56, | |
| 61 | + "y": 496, | |
| 62 | + "width": 160, | |
| 63 | + "height": 48, | |
| 64 | + "rotation": "horizontal", | |
| 65 | + "border": "none", | |
| 66 | + "config": { | |
| 67 | + "barcodeType": "CODE128", | |
| 68 | + "data": "18180817871", | |
| 69 | + "showText": true, | |
| 70 | + "orientation": "horizontal" | |
| 71 | + } | |
| 72 | + }, | |
| 73 | + { | |
| 74 | + "id": "el-1774011878312-5yo0csi", | |
| 31 | 75 | "type": "TEXT_PRODUCT", |
| 32 | - "x": 96, | |
| 33 | - "y": 128, | |
| 76 | + "x": 8, | |
| 77 | + "y": 264, | |
| 78 | + "width": 272, | |
| 79 | + "height": 24, | |
| 80 | + "rotation": "horizontal", | |
| 81 | + "border": "none", | |
| 82 | + "config": { | |
| 83 | + "text": "Cheese Burger Deluxe", | |
| 84 | + "fontFamily": "Arial", | |
| 85 | + "fontSize": 14, | |
| 86 | + "fontWeight": "normal", | |
| 87 | + "textAlign": "left" | |
| 88 | + } | |
| 89 | + }, | |
| 90 | + { | |
| 91 | + "id": "el-1774011967712-m0a2aoy", | |
| 92 | + "type": "TEXT_STATIC", | |
| 93 | + "x": 8, | |
| 94 | + "y": 296, | |
| 34 | 95 | "width": 120, |
| 35 | 96 | "height": 24, |
| 36 | 97 | "rotation": "horizontal", |
| 37 | 98 | "border": "none", |
| 38 | 99 | "config": { |
| 39 | - "text": "商品名", | |
| 100 | + "text": "Ingredients:", | |
| 40 | 101 | "fontFamily": "Arial", |
| 41 | 102 | "fontSize": 14, |
| 42 | 103 | "fontWeight": "normal", |
| ... | ... | @@ -44,16 +105,16 @@ |
| 44 | 105 | } |
| 45 | 106 | }, |
| 46 | 107 | { |
| 47 | - "id": "el-1773998913096-cgabpx1", | |
| 108 | + "id": "el-1774011987154-jzx6iih", | |
| 48 | 109 | "type": "TEXT_STATIC", |
| 49 | 110 | "x": 88, |
| 50 | - "y": 152, | |
| 51 | - "width": 120, | |
| 111 | + "y": 296, | |
| 112 | + "width": 192, | |
| 52 | 113 | "height": 24, |
| 53 | 114 | "rotation": "horizontal", |
| 54 | 115 | "border": "none", |
| 55 | 116 | "config": { |
| 56 | - "text": "文本", | |
| 117 | + "text": "Cheese, Lettuce, Tomato, ", | |
| 57 | 118 | "fontFamily": "Arial", |
| 58 | 119 | "fontSize": 14, |
| 59 | 120 | "fontWeight": "normal", |
| ... | ... | @@ -61,34 +122,141 @@ |
| 61 | 122 | } |
| 62 | 123 | }, |
| 63 | 124 | { |
| 64 | - "id": "el-1773999052674-uzocw1j", | |
| 65 | - "type": "QRCODE", | |
| 66 | - "x": 128, | |
| 67 | - "y": 40, | |
| 68 | - "width": 80, | |
| 69 | - "height": 80, | |
| 125 | + "id": "el-1774012011693-321yub6", | |
| 126 | + "type": "TEXT_STATIC", | |
| 127 | + "x": 8, | |
| 128 | + "y": 328, | |
| 129 | + "width": 256, | |
| 130 | + "height": 24, | |
| 70 | 131 | "rotation": "horizontal", |
| 71 | 132 | "border": "none", |
| 72 | 133 | "config": { |
| 73 | - "data": "12341千问请问抛弃我", | |
| 74 | - "errorLevel": "M" | |
| 134 | + "text": "Beef Pattie, Cucumber.", | |
| 135 | + "fontFamily": "Arial", | |
| 136 | + "fontSize": 14, | |
| 137 | + "fontWeight": "normal", | |
| 138 | + "textAlign": "left" | |
| 75 | 139 | } |
| 76 | 140 | }, |
| 77 | 141 | { |
| 78 | - "id": "el-1773999078958-5tgoru7", | |
| 79 | - "type": "BARCODE", | |
| 80 | - "x": 208, | |
| 81 | - "y": 128, | |
| 142 | + "id": "el-1774012015804-ohlfmqy", | |
| 143 | + "type": "TEXT_STATIC", | |
| 144 | + "x": 8, | |
| 145 | + "y": 360, | |
| 146 | + "width": 232, | |
| 147 | + "height": 24, | |
| 148 | + "rotation": "horizontal", | |
| 149 | + "border": "none", | |
| 150 | + "config": { | |
| 151 | + "text": "Allergens: Milk, Nuts", | |
| 152 | + "fontFamily": "Arial", | |
| 153 | + "fontSize": 14, | |
| 154 | + "fontWeight": "normal", | |
| 155 | + "textAlign": "left" | |
| 156 | + } | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "id": "el-1774012061462-u3cyixy", | |
| 160 | + "type": "TEXT_STATIC", | |
| 161 | + "x": 8, | |
| 162 | + "y": 392, | |
| 163 | + "width": 216, | |
| 164 | + "height": 24, | |
| 165 | + "rotation": "horizontal", | |
| 166 | + "border": "none", | |
| 167 | + "config": { | |
| 168 | + "text": "Preped On: 11/12/25", | |
| 169 | + "fontFamily": "Arial", | |
| 170 | + "fontSize": 14, | |
| 171 | + "fontWeight": "normal", | |
| 172 | + "textAlign": "left" | |
| 173 | + } | |
| 174 | + }, | |
| 175 | + { | |
| 176 | + "id": "el-1774012067221-78hzefs", | |
| 177 | + "type": "TEXT_STATIC", | |
| 178 | + "x": 8, | |
| 179 | + "y": 424, | |
| 180 | + "width": 192, | |
| 181 | + "height": 24, | |
| 182 | + "rotation": "horizontal", | |
| 183 | + "border": "none", | |
| 184 | + "config": { | |
| 185 | + "text": "Must Use By: 13/12/25", | |
| 186 | + "fontFamily": "Arial", | |
| 187 | + "fontSize": 14, | |
| 188 | + "fontWeight": "normal", | |
| 189 | + "textAlign": "left" | |
| 190 | + } | |
| 191 | + }, | |
| 192 | + { | |
| 193 | + "id": "el-1774013823300-e6inmnh", | |
| 194 | + "type": "TEXT_PRICE", | |
| 195 | + "x": 56, | |
| 196 | + "y": 200, | |
| 82 | 197 | "width": 160, |
| 83 | - "height": 48, | |
| 198 | + "height": 56, | |
| 84 | 199 | "rotation": "horizontal", |
| 85 | 200 | "border": "none", |
| 86 | 201 | "config": { |
| 87 | - "barcodeType": "CODE128", | |
| 88 | - "data": "14124151231", | |
| 89 | - "showText": true, | |
| 90 | - "orientation": "horizontal" | |
| 202 | + "text": "9.99", | |
| 203 | + "prefix": "$", | |
| 204 | + "decimal": 2, | |
| 205 | + "fontFamily": "Arial", | |
| 206 | + "fontSize": 30, | |
| 207 | + "fontWeight": "bold", | |
| 208 | + "textAlign": "center" | |
| 209 | + } | |
| 210 | + }, | |
| 211 | + { | |
| 212 | + "id": "el-1774014853682-zc8efun", | |
| 213 | + "type": "TEXT_STATIC", | |
| 214 | + "x": 8, | |
| 215 | + "y": 472, | |
| 216 | + "width": 264, | |
| 217 | + "height": 16, | |
| 218 | + "rotation": "horizontal", | |
| 219 | + "border": "none", | |
| 220 | + "config": { | |
| 221 | + "text": "222 W. Union Street, Concord, NC", | |
| 222 | + "fontFamily": "Arial", | |
| 223 | + "fontSize": 14, | |
| 224 | + "fontWeight": "normal", | |
| 225 | + "textAlign": "center" | |
| 91 | 226 | } |
| 227 | + }, | |
| 228 | + { | |
| 229 | + "id": "el-1774014904848-0ejbswu", | |
| 230 | + "type": "BLANK", | |
| 231 | + "x": 8, | |
| 232 | + "y": 456, | |
| 233 | + "width": 270, | |
| 234 | + "height": 2, | |
| 235 | + "rotation": "horizontal", | |
| 236 | + "border": "line", | |
| 237 | + "config": {} | |
| 238 | + }, | |
| 239 | + { | |
| 240 | + "id": "el-1774015010687-jkd254c", | |
| 241 | + "type": "BLANK", | |
| 242 | + "x": 8, | |
| 243 | + "y": 64, | |
| 244 | + "width": 270, | |
| 245 | + "height": 2, | |
| 246 | + "rotation": "horizontal", | |
| 247 | + "border": "line", | |
| 248 | + "config": {} | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "id": "el-1774015083456-ampm74x", | |
| 252 | + "type": "BLANK", | |
| 253 | + "x": 8, | |
| 254 | + "y": 248, | |
| 255 | + "width": 270, | |
| 256 | + "height": 2, | |
| 257 | + "rotation": "horizontal", | |
| 258 | + "border": "line", | |
| 259 | + "config": {} | |
| 92 | 260 | } |
| 93 | 261 | ] |
| 94 | 262 | } | ... | ... |
美国版/Food Labeling Management App UniApp/src/utils/print/templates/testPrintTemplate.ts
| ... | ... | @@ -4,10 +4,7 @@ import type { LabelTemplateData, StructuredLabelTemplate, SystemLabelTemplate } |
| 4 | 4 | |
| 5 | 5 | export const TEST_PRINT_SYSTEM_TEMPLATE = testTemplateJson as SystemLabelTemplate |
| 6 | 6 | |
| 7 | -export const TEST_PRINT_TEMPLATE_DATA: LabelTemplateData = { | |
| 8 | - productName: '商品名', | |
| 9 | - product: '商品名', | |
| 10 | -} | |
| 7 | +export const TEST_PRINT_TEMPLATE_DATA: LabelTemplateData = {} | |
| 11 | 8 | |
| 12 | 9 | export function createTestPrintTemplate ( |
| 13 | 10 | dpi = 203, | ... | ... |
美国版/Food Labeling Management App UniApp/src/utils/print/tscLabelBuilder.ts
| ... | ... | @@ -5,34 +5,69 @@ |
| 5 | 5 | */ |
| 6 | 6 | import type { MonochromeImageData, PrintImageOptions, StructuredTscTemplate } from './types/printer' |
| 7 | 7 | |
| 8 | -/** 将字符串转为 UTF-8 字节数组(不依赖 TextEncoder) */ | |
| 9 | -function stringToUtf8Bytes (str: string): number[] { | |
| 8 | +function normalizePrinterText (str: string): string { | |
| 9 | + return String(str || '') | |
| 10 | + .normalize('NFKC') | |
| 11 | + .replace(/[\u2018\u2019]/g, '\'') | |
| 12 | + .replace(/[\u201C\u201D]/g, '"') | |
| 13 | + .replace(/[\u2013\u2014]/g, '-') | |
| 14 | +} | |
| 15 | + | |
| 16 | +/** 将字符串转为 Windows-1252 字节数组,优先保证西文特殊字符可打印 */ | |
| 17 | +function stringToPrinterBytes (str: string): number[] { | |
| 18 | + const normalized = normalizePrinterText(str) | |
| 10 | 19 | const out: number[] = [] |
| 11 | - for (let i = 0; i < str.length; i++) { | |
| 12 | - let c = str.charCodeAt(i) | |
| 13 | - if (c < 0x80) { | |
| 14 | - out.push(c) | |
| 15 | - } else if (c < 0x800) { | |
| 16 | - out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)) | |
| 17 | - } else if (c < 0xd800 || c >= 0xe000) { | |
| 18 | - out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)) | |
| 19 | - } else { | |
| 20 | - i++ | |
| 21 | - const c2 = str.charCodeAt(i) | |
| 22 | - const u = ((c & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000 | |
| 23 | - out.push( | |
| 24 | - 0xf0 | (u >> 18), | |
| 25 | - 0x80 | ((u >> 12) & 0x3f), | |
| 26 | - 0x80 | ((u >> 6) & 0x3f), | |
| 27 | - 0x80 | (u & 0x3f) | |
| 28 | - ) | |
| 20 | + const cp1252Map: Record<number, number> = { | |
| 21 | + 0x20ac: 0x80, | |
| 22 | + 0x201a: 0x82, | |
| 23 | + 0x0192: 0x83, | |
| 24 | + 0x201e: 0x84, | |
| 25 | + 0x2026: 0x85, | |
| 26 | + 0x2020: 0x86, | |
| 27 | + 0x2021: 0x87, | |
| 28 | + 0x02c6: 0x88, | |
| 29 | + 0x2030: 0x89, | |
| 30 | + 0x0160: 0x8a, | |
| 31 | + 0x2039: 0x8b, | |
| 32 | + 0x0152: 0x8c, | |
| 33 | + 0x017d: 0x8e, | |
| 34 | + 0x2018: 0x91, | |
| 35 | + 0x2019: 0x92, | |
| 36 | + 0x201c: 0x93, | |
| 37 | + 0x201d: 0x94, | |
| 38 | + 0x2022: 0x95, | |
| 39 | + 0x2013: 0x96, | |
| 40 | + 0x2014: 0x97, | |
| 41 | + 0x02dc: 0x98, | |
| 42 | + 0x2122: 0x99, | |
| 43 | + 0x0161: 0x9a, | |
| 44 | + 0x203a: 0x9b, | |
| 45 | + 0x0153: 0x9c, | |
| 46 | + 0x017e: 0x9e, | |
| 47 | + 0x0178: 0x9f, | |
| 48 | + } | |
| 49 | + | |
| 50 | + for (let i = 0; i < normalized.length; i++) { | |
| 51 | + const code = normalized.charCodeAt(i) | |
| 52 | + if (code < 0x80) { | |
| 53 | + out.push(code) | |
| 54 | + continue | |
| 29 | 55 | } |
| 56 | + if (code >= 0xa0 && code <= 0xff) { | |
| 57 | + out.push(code) | |
| 58 | + continue | |
| 59 | + } | |
| 60 | + if (cp1252Map[code] != null) { | |
| 61 | + out.push(cp1252Map[code]) | |
| 62 | + continue | |
| 63 | + } | |
| 64 | + out.push(0x3f) | |
| 30 | 65 | } |
| 31 | 66 | return out |
| 32 | 67 | } |
| 33 | 68 | |
| 34 | 69 | function addCommandBytes (out: number[], str: string) { |
| 35 | - const bytes = stringToUtf8Bytes(str) | |
| 70 | + const bytes = stringToPrinterBytes(str) | |
| 36 | 71 | for (let i = 0; i < bytes.length; i++) out.push(bytes[i]) |
| 37 | 72 | } |
| 38 | 73 | |
| ... | ... | @@ -89,6 +124,7 @@ export function buildTscLabel (options: { |
| 89 | 124 | |
| 90 | 125 | add(`SIZE ${widthMm} mm,${heightMm} mm`) |
| 91 | 126 | add('GAP 0 mm,0 mm') |
| 127 | + add('CODEPAGE 1252') | |
| 92 | 128 | add('CLS') |
| 93 | 129 | add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"${escapeTscString(productName)}"`) |
| 94 | 130 | y += 35 |
| ... | ... | @@ -118,6 +154,7 @@ export function buildTestTscLabel (): number[] { |
| 118 | 154 | |
| 119 | 155 | add('SIZE 100 mm,65 mm') |
| 120 | 156 | add('GAP 0 mm,0 mm') |
| 157 | + add('CODEPAGE 1252') | |
| 121 | 158 | add('CLS') |
| 122 | 159 | add('BOX 20,20,780,500,3') |
| 123 | 160 | add('BAR 20,90,760,3') |
| ... | ... | @@ -156,6 +193,7 @@ export function buildTscTemplateLabel (template: StructuredTscTemplate): number[ |
| 156 | 193 | |
| 157 | 194 | add(`SIZE ${template.widthMm} mm,${template.heightMm} mm`) |
| 158 | 195 | add(`GAP ${template.gapMm || 0} mm,0 mm`) |
| 196 | + add('CODEPAGE 1252') | |
| 159 | 197 | if (template.density != null) add(`DENSITY ${template.density}`) |
| 160 | 198 | if (template.speed != null) add(`SPEED ${template.speed}`) |
| 161 | 199 | add('CLS') |
| ... | ... | @@ -179,6 +217,14 @@ export function buildTscTemplateLabel (template: StructuredTscTemplate): number[ |
| 179 | 217 | add(`BARCODE ${item.x},${item.y},"${normalizeTscBarcodeType(item.symbology)}",${Math.max(20, Math.round(item.height || 80))},${item.readable === false ? 0 : 1},${item.rotation || 0},${Math.max(1, Math.round(item.narrow || 2))},${Math.max(2, Math.round(item.wide || 2))},"${escapeTscString(item.value)}"`) |
| 180 | 218 | return |
| 181 | 219 | } |
| 220 | + if (item.type === 'bitmap') { | |
| 221 | + const bytesPerRow = item.image.width / 8 | |
| 222 | + const bitmapBytes = pixelsToTscBitmapBytes(item.image) | |
| 223 | + add(`BITMAP ${item.x},${item.y},${bytesPerRow},${item.image.height},0,`) | |
| 224 | + for (let i = 0; i < bitmapBytes.length; i++) out.push(bitmapBytes[i]) | |
| 225 | + out.push(0x0d, 0x0a) | |
| 226 | + return | |
| 227 | + } | |
| 182 | 228 | add(`TEXT ${item.x},${item.y},"${item.font || 'TSS24.BF2'}",${item.rotation || 0},${item.xScale || 1},${item.yScale || 1},"${escapeTscString(item.text)}"`) |
| 183 | 229 | }) |
| 184 | 230 | |
| ... | ... | @@ -225,6 +271,7 @@ export function buildTscImageLabel ( |
| 225 | 271 | |
| 226 | 272 | add(`SIZE ${roundMm(widthMm)} mm,${roundMm(heightMm)} mm`) |
| 227 | 273 | add('GAP 0 mm,0 mm') |
| 274 | + add('CODEPAGE 1252') | |
| 228 | 275 | add('DENSITY 14') |
| 229 | 276 | add('SPEED 5') |
| 230 | 277 | add('CLS') | ... | ... |
美国版/Food Labeling Management App UniApp/src/utils/print/types/printer.ts
| ... | ... | @@ -126,12 +126,20 @@ export interface TscTemplateBarcodeItem { |
| 126 | 126 | wide?: number |
| 127 | 127 | } |
| 128 | 128 | |
| 129 | +export interface TscTemplateBitmapItem { | |
| 130 | + type: 'bitmap' | |
| 131 | + x: number | |
| 132 | + y: number | |
| 133 | + image: MonochromeImageData | |
| 134 | +} | |
| 135 | + | |
| 129 | 136 | export type TscTemplateItem = |
| 130 | 137 | | TscTemplateTextItem |
| 131 | 138 | | TscTemplateBoxItem |
| 132 | 139 | | TscTemplateBarItem |
| 133 | 140 | | TscTemplateQrCodeItem |
| 134 | 141 | | TscTemplateBarcodeItem |
| 142 | + | TscTemplateBitmapItem | |
| 135 | 143 | |
| 136 | 144 | export interface StructuredTscTemplate { |
| 137 | 145 | widthMm: number | ... | ... |